| #!/usr/bin/env python3 |
| # |
| # Intended usage: |
| # |
| # git grep -l '\.qmp(' | xargs ./scripts/python_qmp_updater.py |
| # |
| |
| import re |
| import sys |
| from typing import Optional |
| |
| start_reg = re.compile(r'^(?P<padding> *)(?P<res>\w+) = (?P<vm>.*).qmp\(', |
| flags=re.MULTILINE) |
| |
| success_reg_templ = re.sub('\n *', '', r""" |
| (\n*{padding}(?P<comment>\#.*$))? |
| \n*{padding} |
| ( |
| self.assert_qmp\({res},\ 'return',\ {{}}\) |
| | |
| assert\ {res}\['return'\]\ ==\ {{}} |
| | |
| assert\ {res}\ ==\ {{'return':\ {{}}}} |
| | |
| self.assertEqual\({res}\['return'\],\ {{}}\) |
| )""") |
| |
| some_check_templ = re.sub('\n *', '', r""" |
| (\n*{padding}(?P<comment>\#.*$))? |
| \s*self.assert_qmp\({res},""") |
| |
| |
| def tmatch(template: str, text: str, |
| padding: str, res: str) -> Optional[re.Match[str]]: |
| return re.match(template.format(padding=padding, res=res), text, |
| flags=re.MULTILINE) |
| |
| |
| def find_closing_brace(text: str, start: int) -> int: |
| """ |
| Having '(' at text[start] search for pairing ')' and return its index. |
| """ |
| assert text[start] == '(' |
| |
| height = 1 |
| |
| for i in range(start + 1, len(text)): |
| if text[i] == '(': |
| height += 1 |
| elif text[i] == ')': |
| height -= 1 |
| if height == 0: |
| return i |
| |
| raise ValueError |
| |
| |
| def update(text: str) -> str: |
| result = '' |
| |
| while True: |
| m = start_reg.search(text) |
| if m is None: |
| result += text |
| break |
| |
| result += text[:m.start()] |
| |
| args_ind = m.end() |
| args_end = find_closing_brace(text, args_ind - 1) |
| |
| all_args = text[args_ind:args_end].split(',', 1) |
| |
| name = all_args[0] |
| args = None if len(all_args) == 1 else all_args[1] |
| |
| unchanged_call = text[m.start():args_end+1] |
| text = text[args_end+1:] |
| |
| padding, res, vm = m.group('padding', 'res', 'vm') |
| |
| m = tmatch(success_reg_templ, text, padding, res) |
| |
| if m is None: |
| result += unchanged_call |
| |
| if ('query-' not in name and |
| 'x-debug-block-dirty-bitmap-sha256' not in name and |
| not tmatch(some_check_templ, text, padding, res)): |
| print(unchanged_call + text[:200] + '...\n\n') |
| |
| continue |
| |
| if m.group('comment'): |
| result += f'{padding}{m.group("comment")}\n' |
| |
| result += f'{padding}{vm}.cmd({name}' |
| |
| if args: |
| result += ',' |
| |
| if '\n' in args: |
| m_args = re.search('(?P<pad> *).*$', args) |
| assert m_args is not None |
| |
| cur_padding = len(m_args.group('pad')) |
| expected = len(f'{padding}{res} = {vm}.qmp(') |
| drop = len(f'{res} = ') |
| if cur_padding == expected - 1: |
| # tolerate this bad style |
| drop -= 1 |
| elif cur_padding < expected - 1: |
| # assume nothing to do |
| drop = 0 |
| |
| if drop: |
| args = re.sub('\n' + ' ' * drop, '\n', args) |
| |
| result += args |
| |
| result += ')' |
| |
| text = text[m.end():] |
| |
| return result |
| |
| |
| for fname in sys.argv[1:]: |
| print(fname) |
| with open(fname) as f: |
| t = f.read() |
| |
| t = update(t) |
| |
| with open(fname, 'w') as f: |
| f.write(t) |