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