enforce that style conforms to our coding style
authorMathieu Lacage <mathieu.lacage@sophia.inria.fr>
Mon, 01 Feb 2010 08:04:41 +0100
changeset 5936 66ca055e81c0
parent 5930 f449711db53c
child 5937 aa754f49d2f3
enforce that style conforms to our coding style
utils/check-style.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/utils/check-style.py	Mon Feb 01 08:04:41 2010 +0100
@@ -0,0 +1,439 @@
+#!/usr/bin/env python
+
+import os
+import subprocess
+import tempfile
+import sys
+import filecmp
+import optparse
+import shutil
+import difflib
+import re
+
+def hg_modified_files():
+    files = os.popen ('hg st -nma')
+    return [filename.strip() for filename in files]
+
+def copy_file(filename):
+    [tmp,pathname] = tempfile.mkstemp()
+    src = open(filename, 'r')
+    dst = open(pathname, 'w')
+    for line in src:
+        dst.write(line)
+    dst.close()
+    src.close()
+    return pathname
+
+# generate a temporary configuration file
+def uncrustify_config_file(level):
+    level2 = """
+nl_collapse_empty_body=False
+nl_if_brace=Add
+nl_brace_else=Add
+nl_elseif_brace=Add
+nl_else_brace=Add
+nl_while_brace=Add
+nl_do_brace=Add
+nl_for_brace=Add
+nl_brace_while=Add
+nl_switch_brace=Add
+nl_after_case=True
+nl_namespace_brace=Remove
+nl_after_brace_open=True
+nl_class_leave_one_liners=False
+nl_enum_leave_one_liners=False
+nl_func_leave_one_liners=False
+nl_if_leave_one_liners=False
+nl_class_colon=Ignore
+nl_after_access_spec=1
+nl_after_semicolon=True
+pos_class_colon=Lead
+pos_class_comma=Trail
+pos_bool=Lead
+nl_class_init_args=Add
+nl_template_class=Add
+nl_class_brace=Add
+# does not work very well
+nl_func_type_name=Ignore
+nl_func_scope_name=Ignore
+nl_func_type_name_class=Ignore
+nl_func_proto_type_name=Ignore
+# function\\n(
+nl_func_paren=Remove
+nl_fdef_brace=Add
+nl_struct_brace=Add
+nl_enum_brace=Add
+nl_union_brace=Add
+mod_full_brace_do=Add
+mod_full_brace_for=Add
+mod_full_brace_if=Add
+mod_full_brace_while=Add
+mod_full_brace_for=Add
+mod_remove_extra_semicolon=True
+# max code width
+#code_width=128
+#ls_for_split_full=True
+#ls_func_split_full=True
+"""
+    level1 = """
+# extra spaces here and there
+sp_func_proto_paren=Add
+sp_func_def_paren=Add
+sp_func_call_paren=Add
+sp_brace_typedef=Add
+sp_enum_assign=Add
+sp_before_sparen=Add
+sp_after_semi_for=Add
+sp_arith=Add
+sp_assign=Add
+sp_compare=Add
+sp_cmt_cpp_start=Add
+sp_func_class_paren=Add
+sp_after_type=Add
+sp_type_func=Add
+sp_angle_paren=Add
+"""
+    level0 = """
+sp_after_semi_for=Ignore
+sp_before_sparen=Ignore
+sp_type_func=Ignore
+sp_after_type=Ignore
+nl_class_leave_one_liners=True
+nl_enum_leave_one_liners=True
+nl_func_leave_one_liners=True
+nl_assign_leave_one_liners=True
+#nl_collapse_empty_body=False
+nl_getset_leave_one_liners=True
+nl_if_leave_one_liners=True
+nl_fdef_brace=Ignore
+# finally, indentation configuration
+indent_with_tabs=0
+indent_namespace=false
+indent_columns=2
+indent_brace=2
+indent_case_brace=2
+indent_class=true
+indent_class_colon=True
+# alignment
+indent_align_assign=False
+align_left_shift=True
+# comment reformating disabled
+cmt_reflow_mode=1 # do not touch comments at all
+cmt_indent_multi=False # really, do not touch them
+"""
+    [tmp,pathname] = tempfile.mkstemp()
+    dst = open(pathname, 'w')
+    dst.write(level0)
+    if level >= 1:
+        dst.write(level1)
+    if level >= 2:
+        dst.write(level2)
+    dst.close()
+    return pathname
+
+class PatchChunkLine:
+    SRC = 1
+    DST = 2
+    BOTH = 3
+    def __init__(self):
+        self.__type = 0
+        self.__line = ''
+    def set_src(self,line):
+        self.__type = self.SRC
+        self.__line = line
+    def set_dst(self,line):
+        self.__type = self.DST
+        self.__line = line
+    def set_both(self,line):
+        self.__type = self.BOTH
+        self.__line = line
+    def append_to_line(self, s):
+        self.__line = self.__line + s
+    def line(self):
+        return self.__line
+    def is_src(self):
+        return self.__type == self.SRC or self.__type == self.BOTH
+    def is_dst(self):
+        return self.__type == self.DST or self.__type == self.BOTH
+    def write(self, f):
+        if self.__type == self.SRC:
+            f.write('-%s\n' % self.__line)
+        elif self.__type == self.DST:
+            f.write('+%s\n' % self.__line)
+        elif self.__type == self.BOTH:
+            f.write(' %s\n' % self.__line)
+        else:
+            raise Exception('invalid patch')
+    
+
+class PatchChunk:
+    def __init__(self, src_pos, dst_pos):
+        self.__lines = []
+        self.__src_pos = int(src_pos)
+        self.__dst_pos = int(dst_pos)
+    def src_start(self):
+        return self.__src_pos
+    def add_line(self,line):
+        self.__lines.append(line)
+    def src(self):
+        src = []
+        for line in self.__lines:
+            if line.is_src():
+                src.append(line)
+        return src
+    def dst(self):
+        dst = []
+        for line in self.__lines:
+            if line.is_dst():
+                dst.append(line)
+        return dst
+    def src_len(self):
+        return len(self.src())
+    def dst_len(self):
+        return len(self.dst())
+    def write(self,f):
+        f.write('@@ -%d,%d +%d,%d @@\n' % (self.__src_pos, self.src_len(),
+                                           self.__dst_pos, self.dst_len()))
+        for line in self.__lines:
+            line.write(f)
+
+class Patch:
+    def __init__(self):
+        self.__src = ''
+        self.__dst = ''
+        self.__chunks = []
+    def add_chunk(self, chunk):
+        self.__chunks.append(chunk)
+    def chunks(self):
+        return self.__chunks
+    def set_src(self,src):
+        self.__src = src
+    def set_dst(self,dst):
+        self.__dst = dst
+    def apply(self,filename):
+        # XXX: not implemented
+        return
+    def write(self,f):
+        f.write('--- %s\n' % self.__src )
+        f.write('+++ %s\n' % self.__dst )
+        for chunk in self.__chunks:
+            chunk.write(f)
+
+def parse_patchset(generator):
+    src_file = re.compile('^--- (.*)$')
+    dst_file = re.compile('^\+\+\+ (.*)$')
+    chunk_start = re.compile('^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@')
+    src = re.compile('^-(.*)$')
+    dst = re.compile('^\+(.*)$')
+    both = re.compile('^ (.*)$')
+    patchset = []
+    current_patch = None
+    for line in generator:
+        m = src_file.search(line)
+        if m is not None:
+            current_patch = Patch()
+            patchset.append(current_patch)
+            current_patch.set_src(m.group(1))
+            continue
+        m = dst_file.search(line)
+        if m is not None:
+            current_patch.set_dst(m.group(1))
+            continue
+        m = chunk_start.search(line)
+        if m is not None:
+            current_chunk = PatchChunk(m.group(1), m.group(3))
+            current_patch.add_chunk(current_chunk)
+            continue
+        m = src.search(line)
+        if m is not None:
+            l = PatchChunkLine()
+            l.set_src(m.group(1))
+            current_chunk.add_line(l)
+            continue
+        m = dst.search(line)
+        if m is not None:
+            l = PatchChunkLine()
+            l.set_dst(m.group(1))
+            current_chunk.add_line(l)
+            continue
+        m = both.search(line)
+        if m is not None:
+            l = PatchChunkLine()
+            l.set_both(m.group(1))
+            current_chunk.add_line(l)
+            continue
+        raise Exception()
+    return patchset
+
+def remove_trailing_whitespace_changes(patch_generator):
+    whitespace = re.compile('^(.*)([ \t]+)$')
+    patchset = parse_patchset(patch_generator)
+    for patch in patchset:
+        for chunk in patch.chunks():
+            src = chunk.src()
+            dst = chunk.dst()
+            try:
+                for i in range(0,len(src)):
+                    s = src[i]
+                    d = dst[i]
+                    m = whitespace.search(s.line())
+                    if m is not None and m.group(1) == d.line():
+                        d.append_to_line(m.group(2))
+            except:
+                return patchset
+    return patchset
+
+
+def indent(source, debug, level):
+    output = tempfile.mkstemp()[1]
+    # apply uncrustify
+    cfg = uncrustify_config_file(level)
+    if debug:
+        sys.stderr.write('original file=' + source + '\n')
+        sys.stderr.write('uncrustify config file=' + cfg + '\n')
+        sys.stderr.write('temporary file=' + output + '\n')
+    try:
+        uncrust = subprocess.Popen(['uncrustify', '-c', cfg, '-f', source, '-o', output],
+                                   stdin = subprocess.PIPE,
+                                   stdout = subprocess.PIPE,
+                                   stderr = subprocess.PIPE)
+        (out, err) = uncrust.communicate('')
+        if debug:
+            sys.stderr.write(out)
+            sys.stderr.write(err)
+    except OSError:
+        raise Exception ('uncrustify not installed')
+    # generate a diff file
+    src = open(source, 'r')
+    dst = open(output, 'r')
+    diff = difflib.unified_diff(src.readlines(), dst.readlines(), 
+                                fromfile=source, tofile=output)
+    src.close()
+    dst.close()
+    if debug:
+        initial_diff = tempfile.mkstemp()[1]
+        sys.stderr.write('initial diff file=' + initial_diff + '\n')
+        tmp = open(initial_diff, 'w')
+        tmp.writelines(diff)
+        tmp.close()
+    final_diff = tempfile.mkstemp()[1]
+    if level < 3:
+        patchset = remove_trailing_whitespace_changes(diff);
+        dst = open(final_diff, 'w')
+        if len(patchset) != 0:
+            patchset[0].write(dst)
+        dst.close()
+    else:
+        dst = open(final_diff, 'w')
+        dst.writelines(diff)
+        dst.close()
+            
+            
+    # apply diff file
+    if debug:
+        sys.stderr.write('final diff file=' + final_diff + '\n')
+    shutil.copyfile(source,output)
+    patch = subprocess.Popen(['patch', '-p1', '-i', final_diff, output],
+                             stdin = subprocess.PIPE,
+                             stdout = subprocess.PIPE,
+                             stderr = subprocess.PIPE)
+    (out, err) = patch.communicate('')
+    if debug:
+        sys.stderr.write(out)
+        sys.stderr.write(err)
+    return output
+ 
+
+
+def indent_files(files, diff=False, debug=False, level=0, inplace=False):
+    output = []
+    for f in files:
+        dst = indent(f, debug=debug, level=level)
+        output.append([f,dst])
+
+    # First, copy to inplace
+    if inplace:
+        for src,dst in output:
+            shutil.copyfile(dst,src)
+        return True
+
+    # now, compare
+    failed = []
+    for src,dst in output:
+        if filecmp.cmp(src,dst) == 0:
+            failed.append([src, dst])
+    if len(failed) > 0:
+        if not diff:
+            print 'Found %u badly indented files:' % len(failed)
+            for src,dst in failed:
+                print '  ' + src
+        else:
+            for src,dst in failed:
+                s = open(src, 'r').readlines()
+                d = open(dst, 'r').readlines()
+                for line in difflib.unified_diff(s, d, fromfile=src, tofile=dst):
+                    sys.stdout.write(line)
+        return False
+    return True
+
+def run_as_hg_hook(ui, repo, **kwargs):
+    # hack to work around mercurial < 1.3 bug
+    from mercurial import lock, error
+    lock.LockError = error.LockError
+    # actually do the work
+    files = hg_modified_files()
+    if not indent_files(files, inplace=False):
+        return True
+    return False
+
+def run_as_main():
+    parser = optparse.OptionParser()
+    parser.add_option('--debug', action='store_true', dest='debug', default=False,
+                      help='Output some debugging information')
+    parser.add_option('-l', '--level', type='int', dest='level', default=0,
+                      help="Level of style conformance: higher levels include all lower levels. "
+                      "level=0: re-indent only. level=1: add extra spaces. level=2: insert extra newlines and "
+                      "extra braces around single-line statements. level=3: remove all trailing spaces")
+    parser.add_option('--check-hg-hook', action='store_true', dest='hg_hook', default=False, 
+                      help='Get the list of files to check from mercurial\'s list of modified '
+                      'and added files and assume that the script runs as a pretxncommit mercurial hook')
+    parser.add_option('--check-hg', action='store_true', dest='hg', default=False,
+                      help="Get the list of files to check from mercurial\'s list of modified and added files")
+    parser.add_option('-f', '--check-file', action='store', dest='file', default='',
+                      help="Check a single file")
+    parser.add_option('--diff', action='store_true', dest='diff', default=False,
+                      help="Generate a diff on stdout of the indented files")
+    parser.add_option('-i', '--in-place', action='store_true', dest='in_place', default=False,
+                      help="Indent the input files in-place")
+    (options,args) = parser.parse_args()
+    debug = options.debug
+    if options.hg_hook:
+        files = hg_modified_files()
+        if not indent_files(files, debug=options.debug,
+                            level=options.level,
+                            inplace=False):
+            sys.exit(1)
+    elif options.hg:
+        files = hg_modified_files()
+        indent_files(files, diff=options.diff, 
+                     debug=options.debug,
+                     level=options.level,
+                     inplace=options.in_place)
+    elif options.file != '':
+        file = options.file
+        if not os.path.exists(file) or \
+                not os.path.isfile(file):
+            print 'file %s does not exist' % file
+            sys.exit(1)
+        indent_files([file], diff=options.diff, 
+                     debug=options.debug,
+                     level=options.level,
+                     inplace=options.in_place)
+    sys.exit(0)
+
+if __name__ == '__main__':
+#    try:
+        run_as_main()
+#    except Exception, e:
+#        sys.stderr.write(str(e) + '\n')
+#        sys.exit(1)