#!/usr/bin/env python3 # fix_header_guards.py # Copyright 2024 Andreas Nicolai # # Script to automatically replace the file headers of .h and .cpp files and adjust include guards. # Also checks for correct naming of files. # # Syntax: fix_header_guards.py [--license-file /path/to/LICENSE] # # is the directory with the .h and .cpp files # LICENSE is a file with the license text to insert. import sys import os import argparse import glob import pathlib def baseName(className, namespaceName): if namespaceName == None: return className else: return namespaceName + "_" + className def headerGuard(className, namespaceName): return baseName(className, namespaceName) + "H" def process_header(hdr, LICENSE): hdr_basename = os.path.basename(hdr) # open file and read all lines try: hdr_file = open(hdr, 'r') hdr = hdr.replace('\\', '/') # print ' ' + hdr lines = hdr_file.readlines() # now process all lines and check if there is a class declaration in the file className = None namespaceName = None for line in lines: if len(line.strip()) == 0: continue # class declaration found if line is ended with a { trimmedLine = line.strip() pos = trimmedLine.find("class") if pos != -1: pos2 = trimmedLine.find(':', pos) if pos2 == -1: pos2 = trimmedLine.find('{', pos) if pos2 != -1: if className == None: className = trimmedLine[pos+6:pos2].strip() else: print('WARNING: multiple class declarations in file are not supported.') return pos = trimmedLine.find("namespace") if pos != -1: pos2 = trimmedLine.find('{', pos) if pos2 != -1: if namespaceName == None: namespaceName = trimmedLine[pos+9:pos2].strip() else: print('WARNING: multiple namespace declarations in file are not supported.') return if className == None: print('WARNING: Files without class declaration are not supported.') return # compose header guard hdr_guard = headerGuard(className, namespaceName) basename = baseName(className, namespaceName) # check if filename matches naming convention hdr_filename = basename + ".h" if hdr_filename != hdr_basename: print('WARNING: mismatch of generated file name "'+hdr_filename+'" and file name for file: "'+ hdr_basename+'"') # now create new file content new_content = "" have_header = False skip_next_define = False for line in lines: if line[-1] == '\n': line = line[0:-1] if not have_header: if line.find('#ifndef') == -1: continue have_header = True # now insert license if LICENSE != None: new_content = LICENSE + '\n' # insert header guards new_content = new_content + '#ifndef ' + hdr_guard + '\n' new_content = new_content + '#define ' + hdr_guard + '\n' skip_next_define = True else: if skip_next_define and line.find('#define') != -1: skip_next_define = False continue new_content = new_content + line + '\n' # now substitute closing hdr_guard pos = new_content.rfind('#endif') new_content = new_content[0:pos] + '#endif // ' + hdr_guard + '\n' # write content hdr_file.close() hdr_file = open(hdr, 'w') hdr_file.write(new_content) except IOError as err: print('Cannot open file (check permissions)', hdr) print("I/O error({0}): {1}".format(err)) def process_cpp(cpp, LICENSE): # open file and read all lines try: cpp_file = open(cpp, 'r') cpp = cpp.replace('\\', '/') # print ' ' + cpp lines = cpp_file.readlines() # now create new file content new_content = "" have_header = False for line in lines: if line[-1] == '\n': line = line[0:-1] if not have_header: if line.find('#include') == -1: continue have_header = True # now insert license if LICENSE != None: new_content = LICENSE + '\n' # insert header guards new_content = new_content + line + '\n' else: new_content = new_content + line + '\n' # write content cpp_file.close() cpp_file = open(cpp, 'w') cpp_file.write(new_content) except IOError as err: print('Cannot open file (check permissions)', hdr) print("I/O error({0}): {1}".format(err)) # *** MAIN PROGRAM *** parser = argparse.ArgumentParser(prog='fix_header_guards', description='Replaces header guards and inserts license header') parser.add_argument('-l', '--license-file', type=pathlib.Path, dest='license', help='Path to license file') parser.add_argument('srcdir', type=pathlib.Path, help='Path to sources') args = parser.parse_args() LICENSE = None if args.license != None: if not args.license.exists: print("License file '{}' doesn't exist.".format(args.license)) exit(1) print("Reading license file: {}".format(args.license)) fobj = open(args.license, 'r') LICENSE = fobj.readlines() fobj.close() del fobj LICENSE = "".join(LICENSE) # glob all header files in the directory print('Processing files in directory: {}'.format(args.srcdir)) headers = glob.glob(str(args.srcdir) + "/*.h"); # process all files for hdr in headers: process_header(hdr, LICENSE) cpps = glob.glob(str(args.srcdir) + "/*.cpp"); # process all files for cpp in cpps: process_cpp(cpp, LICENSE)