1#!/usr/bin/env python3 2# 3# Intended usage: 4# 5# git grep -l '\.qmp(' | xargs ./scripts/python_qmp_updater.py 6# 7 8import re 9import sys 10from typing import Optional 11 12start_reg = re.compile(r'^(?P<padding> *)(?P<res>\w+) = (?P<vm>.*).qmp\(', 13 flags=re.MULTILINE) 14 15success_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 28some_check_templ = re.sub('\n *', '', r""" 29 (\n*{padding}(?P<comment>\#.*$))? 30 \s*self.assert_qmp\({res},""") 31 32 33def 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 39def 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 58def 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 128for 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) 137