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