A Discrete-Event Network Simulator
API
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
check-style.py
1 #!/usr/bin/env python
2 
3 import os
4 import subprocess
5 import tempfile
6 import sys
7 import filecmp
8 import optparse
9 import shutil
10 import difflib
11 import re
12 
13 def hg_modified_files():
14  files = os.popen ('hg st -nma')
15  return [filename.strip() for filename in files]
16 
17 def copy_file(filename):
18  [tmp,pathname] = tempfile.mkstemp()
19  src = open(filename, 'r')
20  dst = open(pathname, 'w')
21  for line in src:
22  dst.write(line)
23  dst.close()
24  src.close()
25  return pathname
26 
27 # generate a temporary configuration file
28 def uncrustify_config_file(level):
29  level2 = """
30 nl_collapse_empty_body=False
31 nl_if_brace=Add
32 nl_brace_else=Add
33 nl_elseif_brace=Add
34 nl_else_brace=Add
35 nl_while_brace=Add
36 nl_do_brace=Add
37 nl_for_brace=Add
38 nl_brace_while=Add
39 nl_switch_brace=Add
40 nl_after_case=True
41 nl_namespace_brace=Remove
42 nl_after_brace_open=True
43 nl_class_leave_one_liners=False
44 nl_enum_leave_one_liners=False
45 nl_func_leave_one_liners=False
46 nl_if_leave_one_liners=False
47 nl_class_colon=Ignore
48 nl_after_access_spec=1
49 nl_after_semicolon=True
50 pos_class_colon=Lead
51 pos_class_comma=Trail
52 pos_bool=Lead
53 nl_class_init_args=Add
54 nl_template_class=Add
55 nl_class_brace=Add
56 # does not work very well
57 nl_func_type_name=Ignore
58 nl_func_scope_name=Ignore
59 nl_func_type_name_class=Ignore
60 nl_func_proto_type_name=Ignore
61 # function\\n(
62 nl_func_paren=Remove
63 nl_fdef_brace=Add
64 nl_struct_brace=Add
65 nl_enum_brace=Add
66 nl_union_brace=Add
67 mod_full_brace_do=Add
68 mod_full_brace_for=Add
69 mod_full_brace_if=Add
70 mod_full_brace_while=Add
71 mod_full_brace_for=Add
72 mod_remove_extra_semicolon=True
73 # max code width
74 #code_width=128
75 #ls_for_split_full=True
76 #ls_func_split_full=True
77 """
78  level1 = """
79 # extra spaces here and there
80 sp_brace_typedef=Add
81 sp_enum_assign=Add
82 sp_before_sparen=Add
83 sp_after_semi_for=Add
84 sp_arith=Add
85 sp_assign=Add
86 sp_compare=Add
87 sp_func_class_paren=Add
88 sp_after_type=Add
89 sp_type_func=Add
90 sp_angle_paren=Add
91 """
92  level0 = """
93 sp_func_proto_paren=Add
94 sp_func_def_paren=Add
95 sp_func_call_paren=Add
96 sp_after_semi_for=Ignore
97 sp_before_sparen=Ignore
98 sp_type_func=Ignore
99 sp_after_type=Ignore
100 nl_class_leave_one_liners=True
101 nl_enum_leave_one_liners=True
102 nl_func_leave_one_liners=True
103 nl_assign_leave_one_liners=True
104 #nl_collapse_empty_body=False
105 nl_getset_leave_one_liners=True
106 nl_if_leave_one_liners=True
107 nl_fdef_brace=Ignore
108 # finally, indentation configuration
109 indent_with_tabs=0
110 indent_namespace=false
111 indent_columns=2
112 indent_brace=2
113 indent_case_brace=2
114 indent_class=true
115 indent_class_colon=True
116 # alignment
117 indent_align_assign=False
118 align_left_shift=True
119 # comment reformating disabled
120 cmt_reflow_mode=1 # do not touch comments at all
121 cmt_indent_multi=False # really, do not touch them
122 """
123  [tmp,pathname] = tempfile.mkstemp()
124  dst = open(pathname, 'w')
125  dst.write(level0)
126  if level >= 1:
127  dst.write(level1)
128  if level >= 2:
129  dst.write(level2)
130  dst.close()
131  return pathname
132 
134  SRC = 1
135  DST = 2
136  BOTH = 3
137  def __init__(self):
138  self.__type = 0
139  self.__line = ''
140  def set_src(self,line):
141  self.__type = self.SRC
142  self.__line = line
143  def set_dst(self,line):
144  self.__type = self.DST
145  self.__line = line
146  def set_both(self,line):
147  self.__type = self.BOTH
148  self.__line = line
149  def append_to_line(self, s):
150  self.__line = self.__line + s
151  def line(self):
152  return self.__line
153  def is_src(self):
154  return self.__type == self.SRC or self.__type == self.BOTH
155  def is_dst(self):
156  return self.__type == self.DST or self.__type == self.BOTH
157  def write(self, f):
158  if self.__type == self.SRC:
159  f.write('-%s\n' % self.__line)
160  elif self.__type == self.DST:
161  f.write('+%s\n' % self.__line)
162  elif self.__type == self.BOTH:
163  f.write(' %s\n' % self.__line)
164  else:
165  raise Exception('invalid patch')
166 
167 
169  def __init__(self, src_pos, dst_pos):
170  self.__lines = []
171  self.__src_pos = int(src_pos)
172  self.__dst_pos = int(dst_pos)
173  def src_start(self):
174  return self.__src_pos
175  def add_line(self,line):
176  self.__lines.append(line)
177  def src(self):
178  src = []
179  for line in self.__lines:
180  if line.is_src():
181  src.append(line)
182  return src
183  def dst(self):
184  dst = []
185  for line in self.__lines:
186  if line.is_dst():
187  dst.append(line)
188  return dst
189  def src_len(self):
190  return len(self.src())
191  def dst_len(self):
192  return len(self.dst())
193  def write(self,f):
194  f.write('@@ -%d,%d +%d,%d @@\n' % (self.__src_pos, self.src_len(),
195  self.__dst_pos, self.dst_len()))
196  for line in self.__lines:
197  line.write(f)
198 
199 class Patch:
200  def __init__(self):
201  self.__src = ''
202  self.__dst = ''
203  self.__chunks = []
204  def add_chunk(self, chunk):
205  self.__chunks.append(chunk)
206  def chunks(self):
207  return self.__chunks
208  def set_src(self,src):
209  self.__src = src
210  def set_dst(self,dst):
211  self.__dst = dst
212  def apply(self,filename):
213  # XXX: not implemented
214  return
215  def write(self,f):
216  f.write('--- %s\n' % self.__src )
217  f.write('+++ %s\n' % self.__dst )
218  for chunk in self.__chunks:
219  chunk.write(f)
220 
221 def parse_patchset(generator):
222  src_file = re.compile('^--- (.*)$')
223  dst_file = re.compile('^\+\+\+ (.*)$')
224  chunk_start = re.compile('^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@')
225  src = re.compile('^-(.*)$')
226  dst = re.compile('^\+(.*)$')
227  both = re.compile('^ (.*)$')
228  patchset = []
229  current_patch = None
230  for line in generator:
231  m = src_file.search(line)
232  if m is not None:
233  current_patch = Patch()
234  patchset.append(current_patch)
235  current_patch.set_src(m.group(1))
236  continue
237  m = dst_file.search(line)
238  if m is not None:
239  current_patch.set_dst(m.group(1))
240  continue
241  m = chunk_start.search(line)
242  if m is not None:
243  current_chunk = PatchChunk(m.group(1), m.group(3))
244  current_patch.add_chunk(current_chunk)
245  continue
246  m = src.search(line)
247  if m is not None:
248  l = PatchChunkLine()
249  l.set_src(m.group(1))
250  current_chunk.add_line(l)
251  continue
252  m = dst.search(line)
253  if m is not None:
254  l = PatchChunkLine()
255  l.set_dst(m.group(1))
256  current_chunk.add_line(l)
257  continue
258  m = both.search(line)
259  if m is not None:
260  l = PatchChunkLine()
261  l.set_both(m.group(1))
262  current_chunk.add_line(l)
263  continue
264  raise Exception()
265  return patchset
266 
267 def remove_trailing_whitespace_changes(patch_generator):
268  whitespace = re.compile('^(.*)([ \t]+)$')
269  patchset = parse_patchset(patch_generator)
270  for patch in patchset:
271  for chunk in patch.chunks():
272  src = chunk.src()
273  dst = chunk.dst()
274  try:
275  for i in range(0,len(src)):
276  s = src[i]
277  d = dst[i]
278  m = whitespace.search(s.line())
279  if m is not None and m.group(1) == d.line():
280  d.append_to_line(m.group(2))
281  except:
282  return patchset
283  return patchset
284 
285 
286 def indent(source, debug, level):
287  output = tempfile.mkstemp()[1]
288  # apply uncrustify
289  cfg = uncrustify_config_file(level)
290  if debug:
291  sys.stderr.write('original file=' + source + '\n')
292  sys.stderr.write('uncrustify config file=' + cfg + '\n')
293  sys.stderr.write('temporary file=' + output + '\n')
294  try:
295  uncrust = subprocess.Popen(['uncrustify', '-c', cfg, '-f', source, '-o', output],
296  stdin = subprocess.PIPE,
297  stdout = subprocess.PIPE,
298  stderr = subprocess.PIPE)
299  (out, err) = uncrust.communicate('')
300  if debug:
301  sys.stderr.write(out)
302  sys.stderr.write(err)
303  except OSError:
304  raise Exception ('uncrustify not installed')
305  # generate a diff file
306  src = open(source, 'r')
307  dst = open(output, 'r')
308  diff = difflib.unified_diff(src.readlines(), dst.readlines(),
309  fromfile=source, tofile=output)
310  src.close()
311  dst.close()
312  if debug:
313  initial_diff = tempfile.mkstemp()[1]
314  sys.stderr.write('initial diff file=' + initial_diff + '\n')
315  tmp = open(initial_diff, 'w')
316  tmp.writelines(diff)
317  tmp.close()
318  final_diff = tempfile.mkstemp()[1]
319  if level < 3:
320  patchset = remove_trailing_whitespace_changes(diff);
321  dst = open(final_diff, 'w')
322  if len(patchset) != 0:
323  patchset[0].write(dst)
324  dst.close()
325  else:
326  dst = open(final_diff, 'w')
327  dst.writelines(diff)
328  dst.close()
329 
330 
331  # apply diff file
332  if debug:
333  sys.stderr.write('final diff file=' + final_diff + '\n')
334  shutil.copyfile(source,output)
335  patch = subprocess.Popen(['patch', '-p1', '-i', final_diff, output],
336  stdin = subprocess.PIPE,
337  stdout = subprocess.PIPE,
338  stderr = subprocess.PIPE)
339  (out, err) = patch.communicate('')
340  if debug:
341  sys.stderr.write(out)
342  sys.stderr.write(err)
343  return output
344 
345 
346 
347 def indent_files(files, diff=False, debug=False, level=0, inplace=False):
348  output = []
349  for f in files:
350  dst = indent(f, debug=debug, level=level)
351  output.append([f,dst])
352 
353  # First, copy to inplace
354  if inplace:
355  for src,dst in output:
356  shutil.copyfile(dst,src)
357  return True
358 
359  # now, compare
360  failed = []
361  for src,dst in output:
362  if filecmp.cmp(src,dst) == 0:
363  failed.append([src, dst])
364  if len(failed) > 0:
365  if not diff:
366  print 'Found %u badly indented files:' % len(failed)
367  for src,dst in failed:
368  print ' ' + src
369  else:
370  for src,dst in failed:
371  s = open(src, 'r').readlines()
372  d = open(dst, 'r').readlines()
373  for line in difflib.unified_diff(s, d, fromfile=src, tofile=dst):
374  sys.stdout.write(line)
375  return False
376  return True
377 
378 def run_as_hg_hook(ui, repo, **kwargs):
379  # hack to work around mercurial < 1.3 bug
380  from mercurial import lock, error
381  lock.LockError = error.LockError
382  # actually do the work
383  files = hg_modified_files()
384  if not indent_files(files, inplace=False):
385  return True
386  return False
387 
388 def run_as_main():
389  parser = optparse.OptionParser()
390  parser.add_option('--debug', action='store_true', dest='debug', default=False,
391  help='Output some debugging information')
392  parser.add_option('-l', '--level', type='int', dest='level', default=0,
393  help="Level of style conformance: higher levels include all lower levels. "
394  "level=0: re-indent only. level=1: add extra spaces. level=2: insert extra newlines and "
395  "extra braces around single-line statements. level=3: remove all trailing spaces")
396  parser.add_option('--check-hg-hook', action='store_true', dest='hg_hook', default=False,
397  help='Get the list of files to check from mercurial\'s list of modified '
398  'and added files and assume that the script runs as a pretxncommit mercurial hook')
399  parser.add_option('--check-hg', action='store_true', dest='hg', default=False,
400  help="Get the list of files to check from mercurial\'s list of modified and added files")
401  parser.add_option('-f', '--check-file', action='store', dest='file', default='',
402  help="Check a single file")
403  parser.add_option('--diff', action='store_true', dest='diff', default=False,
404  help="Generate a diff on stdout of the indented files")
405  parser.add_option('-i', '--in-place', action='store_true', dest='in_place', default=False,
406  help="Indent the input files in-place")
407  (options,args) = parser.parse_args()
408  debug = options.debug
409  if options.hg_hook:
410  files = hg_modified_files()
411  if not indent_files(files, debug=options.debug,
412  level=options.level,
413  inplace=False):
414  sys.exit(1)
415  elif options.hg:
416  files = hg_modified_files()
417  indent_files(files, diff=options.diff,
418  debug=options.debug,
419  level=options.level,
420  inplace=options.in_place)
421  elif options.file != '':
422  file = options.file
423  if not os.path.exists(file) or \
424  not os.path.isfile(file):
425  print 'file %s does not exist' % file
426  sys.exit(1)
427  indent_files([file], diff=options.diff,
428  debug=options.debug,
429  level=options.level,
430  inplace=options.in_place)
431  sys.exit(0)
432 
433 if __name__ == '__main__':
434 # try:
435  run_as_main()
436 # except Exception, e:
437 # sys.stderr.write(str(e) + '\n')
438 # sys.exit(1)