#!/usr/bin/env python import os import re import sys from file_types import * cpp_c_headers = { 'assert.h' : 'cassert', 'ctype.h' : 'cctype', 'errno.h' : 'cerrno', 'float.h' : 'cfloat', 'limits.h' : 'climits', 'locale.h' : 'clocale', 'math.h' : 'cmath', 'setjmp.h' : 'csetjmp', 'signal.h' : 'csignal', 'stdarg.h' : 'cstdarg', 'stddef.h' : 'cstddef', 'stdio.h' : 'cstdio', 'stdlib.h' : 'cstdlib', 'string.h' : 'cstring', 'time.h' : 'ctime', 'wchar.h' : 'cwchar', 'wctype.h' : 'cwctype', } include_re = re.compile(r'([#%])(include|import).*[<"](.*)[">]') def include_key(line): '''Mark directories with a leading space so directories are sorted before files''' match = include_re.match(line) assert match, line keyword = match.group(2) include = match.group(3) # Everything but the file part needs to have a space prepended parts = include.split('/') if len(parts) == 2 and parts[0] == 'dnet': # Don't sort the dnet includes with respect to each other, but # make them sorted with respect to non dnet includes. Python # guarantees that sorting is stable, so just clear the # basename part of the filename. parts[1] = ' ' parts[0:-1] = [ ' ' + s for s in parts[0:-1] ] key = '/'.join(parts) return key class SortIncludes(object): # different types of includes for different sorting of headers # - Python header needs to be first if it exists # <*.h> - system headers (directories before files) # <*> - STL headers # <*.(hh|hxx|hpp|H)> - C++ Headers (directories before files) # "*" - M5 headers (directories before files) includes_re = ( ('python', '<>', r'^(#include)[ \t]+<(Python.*\.h)>(.*)'), ('c', '<>', r'^(#include)[ \t]<(.+\.h)>(.*)'), ('stl', '<>', r'^(#include)[ \t]+<([0-9A-z_]+)>(.*)'), ('cc', '<>', r'^(#include)[ \t]+<([0-9A-z_]+\.(hh|hxx|hpp|H))>(.*)'), ('m5cc', '""', r'^(#include)[ \t]"(.+\.h{1,2})"(.*)'), ('swig0', '<>', r'^(%import)[ \t]<(.+)>(.*)'), ('swig1', '<>', r'^(%include)[ \t]<(.+)>(.*)'), ('swig2', '""', r'^(%import)[ \t]"(.+)"(.*)'), ('swig3', '""', r'^(%include)[ \t]"(.+)"(.*)'), ) # compile the regexes includes_re = tuple((a, b, re.compile(c)) for a,b,c in includes_re) def __init__(self): self.reset() def reset(self): # clear all stored headers self.includes = {} for include_type,_,_ in self.includes_re: self.includes[include_type] = [] def dump_block(self): '''dump the includes''' first = True for include,_,_ in self.includes_re: if not self.includes[include]: continue if not first: # print a newline between groups of # include types yield '' first = False # print out the includes in the current group # and sort them according to include_key() prev = None for l in sorted(self.includes[include], key=include_key): if l != prev: yield l prev = l def __call__(self, lines, filename, language): leading_blank = False blanks = 0 block = False for line in lines: if not line: blanks += 1 if not block: # if we're not in an include block, spit out the # newline otherwise, skip it since we're going to # control newlines withinin include block yield '' continue # Try to match each of the include types for include_type,(ldelim,rdelim),include_re in self.includes_re: match = include_re.match(line) if not match: continue # if we've got a match, clean up the #include line, # fix up stl headers and store it in the proper category groups = match.groups() keyword = groups[0] include = groups[1] extra = groups[-1] if include_type == 'c' and language == 'C++': stl_inc = cpp_c_headers.get(include, None) if stl_inc: include = stl_inc include_type = 'stl' line = keyword + ' ' + ldelim + include + rdelim + extra self.includes[include_type].append(line) # We've entered a block, don't keep track of blank # lines while in a block block = True blanks = 0 break else: # this line did not match a #include assert not include_re.match(line) # if we're not in a block and we didn't match an include # to enter a block, just emit the line and continue if not block: yield line continue # We've exited an include block. for block_line in self.dump_block(): yield block_line # if there are any newlines after the include block, # emit a single newline (removing extras) if blanks and block: yield '' blanks = 0 block = False self.reset() # emit the line that ended the block yield line if block: # We've exited an include block. for block_line in self.dump_block(): yield block_line # default language types to try to apply our sorting rules to default_languages = frozenset(('C', 'C++', 'isa', 'python', 'scons', 'swig')) def options(): import optparse options = optparse.OptionParser() add_option = options.add_option add_option('-d', '--dir_ignore', metavar="DIR[,DIR]", type='string', default=','.join(default_dir_ignore), help="ignore directories") add_option('-f', '--file_ignore', metavar="FILE[,FILE]", type='string', default=','.join(default_file_ignore), help="ignore files") add_option('-l', '--languages', metavar="LANG[,LANG]", type='string', default=','.join(default_languages), help="languages") add_option('-n', '--dry-run', action='store_true', help="don't overwrite files") return options def parse_args(parser): opts,args = parser.parse_args() opts.dir_ignore = frozenset(opts.dir_ignore.split(',')) opts.file_ignore = frozenset(opts.file_ignore.split(',')) opts.languages = frozenset(opts.languages.split(',')) return opts,args if __name__ == '__main__': parser = options() opts, args = parse_args(parser) for base in args: for filename,language in find_files(base, languages=opts.languages, file_ignore=opts.file_ignore, dir_ignore=opts.dir_ignore): if opts.dry_run: print "%s: %s" % (filename, language) else: update_file(filename, filename, language, SortIncludes())