| #!/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) |