#!/usr/bin/env python import argparse import os import re import sys HELPTEXT = \ '''A script to add a new C++ class to an engine. Creates the .cpp and .h with copyright notices, header guards, and namespace, and updates module.mk.''' # This copyright notice is used by the script but also applies to this file. COPYRIGHT = \ '''/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */''' def get_engine_path(engname): engname = engname.lower() curpath = os.path.realpath(os.path.curdir) pathelems = curpath.split(os.path.sep) result = None if 'engines' in pathelems: # already in engines result = os.path.join(*(pathelems[:pathelems.index('engines') + 1] + [engname])) elif 'devtools' in pathelems: result = os.path.join(*(pathelems[:pathelems.index('devtools')] + ['engines', engname])) elif 'scummvm' in pathelems: result = os.path.join(*(pathelems[:pathelems.index('scummvm') + 1] + ['engines', engname])) if result and curpath.startswith('/'): # put the / back on the start result = '/' + result if not result or not os.path.isdir(result): result = None return result CPP_TEMPLATE = \ '''{copyright} #include "{headerpath}/{headerfilename}" namespace {namespace} {{ {classname}::{classname}() {{ }} // TODO: Add more functions here. }} // end namespace {namespace} ''' H_TEMPLATE = \ '''{copyright} #ifndef {headername} #define {headername} namespace {namespace} {{ class {classname} {{ public: {classname}(); // TODO add public members private: // TODO add private members }}; }} // end namespace {namespace} #endif // {headername} ''' def class_to_fbase(classname): '''Create class_file_pattern from camelcase classname''' return re.sub('([A-Z])', '_\\1', classname).lower()[1:] def create_files(destpath, classname, namespace, enginename): '''Create the cpp and h files from the templates''' fbase = class_to_fbase(classname) fname_cpp = os.path.join(destpath, fbase + '.cpp') fname_h = os.path.join(destpath, fbase + '.h') headerpath = destpath[destpath.index(enginename):] headername = headerpath.upper().replace('/', '_') + '_' + fbase.upper() + '_H' if os.path.exists(fname_cpp): print('ERROR: cpp file %s already exists' % fname_cpp, file=sys.stderr) return False if os.path.exists(fname_h): print('ERROR: header file %s already exists' % fname_h, file=sys.stderr) return False cppstr = CPP_TEMPLATE.format(copyright=COPYRIGHT, classname=classname, namespace=namespace, headerpath=headerpath, headerfilename=(fbase+'.h')) hstr = H_TEMPLATE.format(copyright=COPYRIGHT, classname=classname, namespace=namespace, headername=headername) print("Creating %s" % fname_cpp) with open(fname_cpp, 'w') as cppfile: cppfile.write(cppstr) print("Creating %s" % fname_h) with open(fname_h, 'w') as hfile: hfile.write(hstr) return True high_sort_key = chr(0x7e) def module_sort_key(mod): ''' Sort key for .o files in module.mk Files in a directory always before sub-dir contents. Alphabetical order otherwise. ''' parts = mod.split('/') nparts = len(parts) key = '' for i in range(nparts): if i < nparts - 1: key += high_sort_key + parts[i] + '/' else: key += parts[i] return key def update_module_mk(module_mk_path, classname, subpath): '''Add the new object file to module.mk's obj file list''' fbase = class_to_fbase(classname) o_file_path = fbase + '.o' if subpath != '.': o_file_path = os.path.join(subpath, o_file_path) module_mk_contents = open(module_mk_path).read() module_mk_contents = module_mk_contents.split('\n') mod_obs_start = -1 mod_obs_end = -1 for lineno, line in enumerate(module_mk_contents): if line.startswith('MODULE_OBJS := '): mod_obs_start = lineno break if mod_obs_start == -1: print('ERROR: MODULE_OBJS line not found in %s?' % module_mk_path) return False for lineno, line in enumerate(module_mk_contents[mod_obs_start:]): if not line: mod_obs_end = lineno break if mod_obs_end == -1: print('ERROR: MODULE_OBJS never ends in %s?' % module_mk_path) return False before = module_mk_contents[:mod_obs_start+1] modules = module_mk_contents[mod_obs_start+1:mod_obs_end] after = module_mk_contents[mod_obs_end:] modules.append('\t%s \\' % o_file_path) modules.sort(key=module_sort_key) print("Adding %s to module.mk" % o_file_path) open(module_mk_path, 'w').write('\n'.join(before + modules + after)) return True def main(): parser = argparse.ArgumentParser(description=HELPTEXT) parser.add_argument('engine', help='name of the engine') parser.add_argument('subpath', help='subpath within the engine (use \'.\' to add to the engine root)') parser.add_argument('classname', help='CamelCase name of the class to add. Filenames will be lower_case (capitals become _)') parser.add_argument('--namespace', '-n', help='Namespace tp use (default is name of engine with a capital letter start)') args = parser.parse_args() engpath = get_engine_path(args.engine) if not engpath: parser.error("Can't find path for engine %s. Run inside scummvm checkout and ensure engine exists" % (args.engine,)) module_mk = os.path.join(engpath, 'module.mk') if not os.path.exists(module_mk): parser.error("Can't find module.mk inside engine %s, help!" % (args.engine,)) destpath = engpath if not args.subpath == '.': destpath = destpath + os.path.sep + args.subpath if not os.path.isdir(destpath): parser.error("Subdir %s doesn't exist in engine %s, try making it first." % (args.subpath, args.engine,)) if not args.classname[0].isupper(): parser.error("Class name %s should be CamelCase" % (args.classname,)) namespace = args.namespace or args.engine.capitalize() if not create_files(destpath, args.classname, namespace, args.engine): return 1 if not update_module_mk(module_mk, args.classname, args.subpath): return 1 return 0 if __name__ == '__main__': sys.exit(main())