blob: 15d1f2788c374e5540c834ad62fd5cbf06bce679 [file] [log] [blame]
#!/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.h> - 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())