#!/bin/env python # Copyright (c) 2010 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a file with MOCK_METHODX(...) macros for a COM interface. For interfaces that will not change, you can run this file by hand. For interfaces that may still change, you could run it as part of your build system to generate code, then #include the generated list of methods inside an appropriate wrapper class. Usage: com_mock.py INTERFACE_NAME HEADER [ADD_HEADERS]* > output-inl.h """ import re import sys # Finds an interface block in a header file generated by IDL. _INTERFACE_PATTERN = (r'MIDL_INTERFACE\("[A-Za-z0-9\-]+"\)\s*%s\s*:\s*' r'public\s*(?P\w+)\s*{\s*public:' r'(?P[^}]+)};') # Parses out a method from within an interface block. _METHOD_RE = re.compile(r'virtual\s*(:?/\*[^*]+\*/)?\s*(?P\w+)\s*' r'STDMETHODCALLTYPE\s*(?P\w+)\s*\(' r'(?P[^\)]+)\)\s*=\s*0;') # Finds inline C-style comments. _COMMENTS_RE = re.compile('/\*.*?\*/') # Finds __RPC_xyz defines. _RPC_RE = re.compile(' __RPC\w*') # Finds whitespace. _WHITESPACE_RE = re.compile('\s+') # Finds default argument values. _DEFAULT_ARG_RE = re.compile('\s*=\s*[^,]*') # Header for generated files. _FILE_HEADER = '''\ // Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // Auto-generated by com_mock.py ''' class Mocker(object): """Given a set of header files, can generate mocky goodness for interfaces defined in them. """ def __init__(self): self.headers_ = '' def AddHeaders(self, header_files): """Adds the specified files to the headers to parse.""" assert header_files # Slurp all the header files into a single huge big string. # Yummy nonperformant code - but it probably doesn't need to be. headers = [] for f in header_files: fo = open(f, 'r') headers.append(fo.read()) fo.close() self.headers_ += '\n'.join(headers) def _GetInterfaceInfo(self, interface_name): """Returns a tuple (base_interface, method_block) or None.""" m = re.search(_INTERFACE_PATTERN % interface_name, self.headers_) if m: return (m.group('base'), m.group('method_block')) else: return None @staticmethod def _ProcessParams(params): """Return a tuple (count, cleaned_up_params).""" if params == ' void': return (0, '') else: params = params.replace('\n', ' ') params = _COMMENTS_RE.sub('', params) params = _RPC_RE.sub(' ', params) params = _WHITESPACE_RE.sub(' ', params) params = _DEFAULT_ARG_RE.sub(' ', params) return (params.count(',') + 1, params.strip()) def _MockMethodsInBlock(self, block): """Return a list of mock functions for each of the methods defined in 'block', which is the text between 'public:' and '};' in an interface definition. """ methods = [] for m in _METHOD_RE.finditer(block): return_type = m.group('ret') name = m.group('name') params = m.group('params') (param_count, clean_params) = self._ProcessParams(params) method_definition = ('MOCK_METHOD%d_WITH_CALLTYPE(__stdcall, %s, %s(%s));' % (param_count, name, return_type, clean_params)) if len(method_definition) > 78: method_definition = ( 'MOCK_METHOD%d_WITH_CALLTYPE(__stdcall, %s, %s(\n %s));' % (param_count, name, return_type, clean_params)) methods.append(method_definition) return methods def MockInterface(self, name): """Returns a list of all the mock methods needed for the given interface (including methods from inherited interfaces, but stopping short of IDispatch and IUnknown). """ info = self._GetInterfaceInfo(name) if not info: return '' # Generate inherited methods methods = [] if info[0] not in ('IUnknown', 'IDispatch'): methods.extend(self.MockInterface(info[0])) methods.extend(self._MockMethodsInBlock(info[1])) return methods def MockMethods(interface_name, header_files): """Returns a string with a correctly filled-out MOCK_METHODX(...) line for each method in the given interface, plus all of its inherited interfaces, terminating when IDispatch or IUnknown is reached. You must list all header files required to find the interface itself and all of the interfaces it inherits from, except IDispatch and IUnknown. Header files must be IDL-generated for the pattern matching used in the code to work correctly. Args: interface_name: 'IWebBrowser2' header_files: ['c:\\platform_sdk\\files\\Include\\exdisp.h', 'c:\\platform_sdk\\files\\Include\\oaidl.h'] Returns: 'MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetWindow, HRESULT()); ...' """ mocker = Mocker() mocker.AddHeaders(header_files) return '\n'.join(mocker.MockInterface(interface_name)) def Main(args): if not args or len(args) < 2: print __doc__ return 1 else: print _FILE_HEADER print MockMethods(args[0], args[1:]) return 0 if __name__ == '__main__': sys.exit(Main(sys.argv[1:]))