A Discrete-Event Network Simulator
API
check-style.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
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 
14  files = os.popen('git diff --name-only')
15  process = subprocess.Popen(["git","rev-parse","--show-toplevel"],
16  stdout = subprocess.PIPE,
17  stderr = subprocess.PIPE)
18  root_dir, _ = process.communicate()
19  if isinstance(root_dir, bytes):
20  root_dir=root_dir.decode("utf-8")
21  files_changed = [item.strip() for item in files.readlines()]
22  files_changed = [item for item in files_changed if item.endswith('.h') or item.endswith('.cc')]
23  return [root_dir[: -1] + "/" + filename.strip () for filename in files_changed]
24 
25 def copy_file(filename):
26  _, pathname = tempfile.mkstemp()
27  with open(filename, 'r') as src, open(pathname, 'w') as dst:
28  for line in src:
29  dst.write(line)
30  return pathname
31 
32 # generate a temporary configuration file
34  level2 = """
35 nl_if_brace=Add
36 nl_brace_else=Add
37 nl_elseif_brace=Add
38 nl_else_brace=Add
39 nl_while_brace=Add
40 nl_do_brace=Add
41 nl_for_brace=Add
42 nl_brace_while=Add
43 nl_switch_brace=Add
44 nl_after_case=True
45 nl_namespace_brace=ignore
46 nl_after_brace_open=True
47 nl_class_leave_one_liners=False
48 nl_enum_leave_one_liners=False
49 nl_func_leave_one_liners=False
50 nl_if_leave_one_liners=False
51 nl_class_colon=Ignore
52 nl_before_access_spec=2
53 nl_after_access_spec=0
54 indent_access_spec=-indent_columns
55 nl_after_semicolon=True
56 pos_class_colon=Lead
57 pos_class_comma=Trail
58 indent_constr_colon=true
59 pos_bool=Lead
60 nl_class_init_args=Add
61 nl_template_class=Add
62 nl_class_brace=Add
63 # does not work very well
64 nl_func_type_name=Ignore
65 nl_func_scope_name=Ignore
66 nl_func_type_name_class=Ignore
67 nl_func_proto_type_name=Ignore
68 # function\\n(
69 nl_func_paren=Remove
70 nl_fdef_brace=Add
71 nl_struct_brace=Add
72 nl_enum_brace=Add
73 nl_union_brace=Add
74 mod_full_brace_do=Add
75 mod_full_brace_for=Add
76 mod_full_brace_if=Add
77 mod_full_brace_while=Add
78 mod_full_brace_for=Add
79 mod_remove_extra_semicolon=True
80 # max code width
81 #code_width=128
82 #ls_for_split_full=True
83 #ls_func_split_full=True
84 nl_cpp_lambda_leave_one_liners=True
85 """
86  level1 = """
87 # extra spaces here and there
88 sp_brace_typedef=Add
89 sp_enum_assign=Add
90 sp_before_sparen=Add
91 sp_after_semi_for=Add
92 sp_arith=Add
93 sp_assign=Add
94 sp_compare=Add
95 sp_func_class_paren=Add
96 sp_after_type=Add
97 sp_type_func=Add
98 sp_angle_paren=Add
99 """
100  level0 = """
101 sp_func_proto_paren=Add
102 sp_func_def_paren=Add
103 sp_func_call_paren=Add
104 sp_after_semi_for=Ignore
105 sp_before_sparen=Ignore
106 sp_before_ellipsis=Remove
107 sp_type_func=Ignore
108 sp_after_type=Ignore
109 nl_class_leave_one_liners=True
110 nl_enum_leave_one_liners=True
111 nl_func_leave_one_liners=True
112 nl_assign_leave_one_liners=True
113 nl_collapse_empty_body=True
114 nl_getset_leave_one_liners=True
115 nl_if_leave_one_liners=True
116 nl_fdef_brace=Ignore
117 # finally, indentation configuration
118 indent_with_tabs=0
119 indent_namespace=false
120 indent_columns=2
121 indent_brace=2
122 indent_case_brace=indent_columns
123 indent_class=true
124 indent_class_colon=True
125 indent_switch_case=indent_columns
126 # alignment
127 indent_align_assign=False
128 align_left_shift=True
129 # comment reformating disabled
130 cmt_reflow_mode=1 # do not touch comments at all
131 cmt_indent_multi=False # really, do not touch them
132 disable_processing_cmt= " *NS_CHECK_STYLE_OFF*"
133 enable_processing_cmt= " *NS_CHECK_STYLE_ON*"
134 """
135  _, pathname = tempfile.mkstemp()
136  with open(pathname, 'w') as dst:
137  dst.write(level0)
138  if level >= 1:
139  dst.write(level1)
140  if level >= 2:
141  dst.write(level2)
142  return pathname
143 
144 
146 
152  SRC = 1
153 
155  DST = 2
156 
158  BOTH = 3
159  def __init__(self):
160  """! Initializer
161  @param self The current class
162  """
163  self.__type__type = 0
164  self.__line__line = ''
165  def set_src(self,line):
166  """! Set source
167  @param self The current class
168  @param line source line
169  @return none
170  """
171  self.__type__type = self.SRCSRC
172  self.__line__line = line
173  def set_dst(self,line):
174  """! Set destination
175  @param self The current class
176  @param line destination line
177  @return none
178  """
179  self.__type__type = self.DSTDST
180  self.__line__line = line
181  def set_both(self,line):
182  """! Set both
183  @param self The current class
184  @param line
185  @return none
186  """
187  self.__type__type = self.BOTHBOTH
188  self.__line__line = line
189  def append_to_line(self, s):
190  """! Append to line
191  @param self The current class
192  @param s line to append
193  @return none
194  """
195  self.__line__line = self.__line__line + s
196  def line(self):
197  """! Get line
198  @param self The current class
199  @return line
200  """
201  return self.__line__line
202  def is_src(self):
203  """! Is source
204  @param self The current class
205  @return true if type is source
206  """
207  return self.__type__type == self.SRCSRC or self.__type__type == self.BOTHBOTH
208  def is_dst(self):
209  """! Is destination
210  @param self The current class
211  @return true if type is destination
212  """
213  return self.__type__type == self.DSTDST or self.__type__type == self.BOTHBOTH
214  def write(self, f):
215  """! Write to file
216  @param self The current class
217  @param f file
218  @return exception if invalid type
219  """
220  if self.__type__type == self.SRCSRC:
221  f.write('-%s\n' % self.__line__line)
222  elif self.__type__type == self.DSTDST:
223  f.write('+%s\n' % self.__line__line)
224  elif self.__type__type == self.BOTHBOTH:
225  f.write(' %s\n' % self.__line__line)
226  else:
227  raise Exception('invalid patch')
228 
229 
231 
241  def __init__(self, src_pos, dst_pos):
242  """! Initializer
243  @param self: this object
244  @param src_pos: source position
245  @param dst_pos: destination position
246  """
247  self.__lines__lines = []
248  self.__src_pos__src_pos = int(src_pos)
249  self.__dst_pos__dst_pos = int(dst_pos)
250  def src_start(self):
251  """! Source start function
252  @param self this object
253  @return source position
254  """
255  return self.__src_pos__src_pos
256  def add_line(self,line):
257  """! Add line function
258  @param self The current class
259  @param line line to add
260  @return none
261  """
262  self.__lines__lines.append(line)
263  def src(self):
264  """! Get source lines
265  @param self The current class
266  @return the source lines
267  """
268  src = []
269  for line in self.__lines__lines:
270  if line.is_src():
271  src.append(line)
272  return src
273  def dst(self):
274  """! Get destination lines
275  @param self The current class
276  @return the destination lines
277  """
278  dst = []
279  for line in self.__lines__lines:
280  if line.is_dst():
281  dst.append(line)
282  return dst
283  def src_len(self):
284  """! Get number of source lines
285  @param self The current class
286  @return number of source lines
287  """
288  return len(self.srcsrc())
289  def dst_len(self):
290  """! Get number of destinaton lines
291  @param self The current class
292  @return number of destination lines
293  """
294  return len(self.dstdst())
295  def write(self,f):
296  """! Write lines to file
297  @param self The current class
298  @param f: file to write to
299  @return none
300  """
301  f.write('@@ -%d,%d +%d,%d @@\n' % (self.__src_pos__src_pos, self.src_lensrc_len(),
302  self.__dst_pos__dst_pos, self.dst_lendst_len()))
303  for line in self.__lines__lines:
304  line.write(f)
305 
306 
307 class Patch:
308 
314  def __init__(self):
315  """! Initializer
316  @param self The current class
317  """
318  self.__src__src = ''
319  self.__dst__dst = ''
320  self.__chunks__chunks = []
321  def add_chunk(self, chunk):
322  """! Add chunk
323  @param self this object
324  @param chunk chunk
325  @return none
326  """
327  self.__chunks__chunks.append(chunk)
328  def chunks(self):
329  """! Get the chunks
330  @param self The current class
331  @return the chunks
332  """
333  return self.__chunks__chunks
334  def set_src(self,src):
335  """! Set source
336  @param self this object
337  @param src source
338  @return none
339  """
340  self.__src__src = src
341  def set_dst(self,dst):
342  """! Set destination
343  @param self this object
344  @param dst destintion
345  @return none
346  """
347  self.__dst__dst = dst
348  def apply(self,filename):
349  """! Apply function
350  @param self The current class
351  @param filename file name
352  @return none
353  """
354  # XXX: not implemented
355  return
356  def write(self,f):
357  """! Write to file
358  @param self The current class
359  @param f the file
360  @return none
361  """
362  f.write('--- %s\n' % self.__src )
363  f.write('+++ %s\n' % self.__dst )
364  for chunk in self.__chunks:
365  chunk.write(f)
366 
367 def parse_patchset(generator):
368  src_file = re.compile('^--- (.*)$')
369  dst_file = re.compile('^\+\+\+ (.*)$')
370  chunk_start = re.compile('^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@')
371  src = re.compile('^-(.*)$')
372  dst = re.compile('^\+(.*)$')
373  both = re.compile('^ (.*)$')
374  patchset = []
375  current_patch = None
376  for line in generator:
377  m = src_file.search(line)
378  if m is not None:
379  current_patch = Patch()
380  patchset.append(current_patch)
381  current_patch.set_src(m.group(1))
382  continue
383  m = dst_file.search(line)
384  if m is not None:
385  current_patch.set_dst(m.group(1))
386  continue
387  m = chunk_start.search(line)
388  if m is not None:
389  current_chunk = PatchChunk(m.group(1), m.group(3))
390  current_patch.add_chunk(current_chunk)
391  continue
392  m = src.search(line)
393  if m is not None:
394  l = PatchChunkLine()
395  l.set_src(m.group(1))
396  current_chunk.add_line(l)
397  continue
398  m = dst.search(line)
399  if m is not None:
400  l = PatchChunkLine()
401  l.set_dst(m.group(1))
402  current_chunk.add_line(l)
403  continue
404  m = both.search(line)
405  if m is not None:
406  l = PatchChunkLine()
407  l.set_both(m.group(1))
408  current_chunk.add_line(l)
409  continue
410  raise Exception()
411  return patchset
412 
414  whitespace = re.compile('^(.*)([ \t]+)$')
415  patchset = parse_patchset(patch_generator)
416  for patch in patchset:
417  for chunk in patch.chunks():
418  src = chunk.src()
419  dst = chunk.dst()
420  try:
421  for i in range(0,len(src)):
422  s = src[i]
423  d = dst[i]
424  m = whitespace.search(s.line())
425  if m is not None and m.group(1) == d.line():
426  d.append_to_line(m.group(2))
427  except:
428  return patchset
429  return patchset
430 
431 
432 def indent(source, debug, level):
433  output = tempfile.mkstemp()[1]
434  # apply uncrustify
435  cfg = uncrustify_config_file(level)
436  if debug:
437  sys.stderr.write('original file=' + source + '\n')
438  sys.stderr.write('uncrustify config file=' + cfg + '\n')
439  sys.stderr.write('temporary file=' + output + '\n')
440  try:
441  uncrust = subprocess.Popen(['uncrustify', '-c', cfg, '-f', source, '-o', output],
442  stdin = subprocess.PIPE,
443  stdout = subprocess.PIPE,
444  stderr = subprocess.PIPE,
445  universal_newlines = True)
446  (out, err) = uncrust.communicate('')
447  if debug:
448  sys.stderr.write(out)
449  sys.stderr.write(err)
450  except OSError:
451  raise Exception ('uncrustify not installed')
452  # generate a diff file
453  with open(source, 'r') as src, open(output, 'r') as dst:
454  diff = difflib.unified_diff(src.readlines(), dst.readlines(),
455  fromfile=source, tofile=output)
456  if debug:
457  initial_diff = tempfile.mkstemp()[1]
458  sys.stderr.write('initial diff file=' + initial_diff + '\n')
459  with open(initial_diff, 'w') as tmp:
460  tmp.writelines(diff)
461  final_diff = tempfile.mkstemp()[1]
462  if level < 3:
463  patchset = remove_trailing_whitespace_changes(diff)
464  if len(patchset) != 0:
465  with open(final_diff, 'w') as dst:
466  patchset[0].write(dst)
467  else:
468  with open(final_diff, 'w') as dst:
469  dst.writelines(diff)
470 
471 
472  # apply diff file
473  if debug:
474  sys.stderr.write('final diff file=' + final_diff + '\n')
475  shutil.copyfile(source,output)
476  patch = subprocess.Popen(['patch', '-p1', '-i', final_diff, output],
477  stdin = subprocess.PIPE,
478  stdout = subprocess.PIPE,
479  stderr = subprocess.PIPE,
480  universal_newlines = True)
481  (out, err) = patch.communicate('')
482  if debug:
483  sys.stderr.write(out)
484  sys.stderr.write(err)
485  return output
486 
487 
488 
489 def indent_files(files, diff=False, debug=False, level=0, inplace=False):
490  output = []
491  for f in files:
492  dst = indent(f, debug=debug, level=level)
493  output.append([f,dst])
494 
495  # First, copy to inplace
496  if inplace:
497  for src,dst in output:
498  shutil.copyfile(dst,src)
499  return True
500 
501  # now, compare
502  failed = []
503  for src,dst in output:
504  if filecmp.cmp(src,dst) == 0:
505  failed.append([src, dst])
506  if len(failed) > 0:
507  if not diff:
508  print('Found %u badly indented files:' % len(failed))
509  for src,dst in failed:
510  print(' ' + src)
511  else:
512  for src,dst in failed:
513  with open(src, 'r') as f_src, open(dst, 'r') as f_dst:
514  s = f_src.readlines()
515  d = f_dst.readlines()
516  for line in difflib.unified_diff(s, d, fromfile=src, tofile=dst):
517  sys.stdout.write(line)
518  return False
519  return True
520 
522  parser = optparse.OptionParser()
523  parser.add_option('--debug', action='store_true', dest='debug', default=False,
524  help='Output some debugging information')
525  parser.add_option('-l', '--level', type='int', dest='level', default=0,
526  help="Level of style conformance: higher levels include all lower levels. "
527  "level=0: re-indent only. level=1: add extra spaces. level=2: insert extra newlines and "
528  "extra braces around single-line statements. level=3: remove all trailing spaces")
529  parser.add_option('--check-git', action='store_true', dest='git', default=False,
530  help="Get the list of files to check from Git\'s list of modified and added files")
531  parser.add_option('-f', '--check-file', action='store', dest='file', default='',
532  help="Check a single file")
533  parser.add_option('--diff', action='store_true', dest='diff', default=False,
534  help="Generate a diff on stdout of the indented files")
535  parser.add_option('-i', '--in-place', action='store_true', dest='in_place', default=False,
536  help="Indent the input files in-place")
537  options, _ = parser.parse_args()
538  style_is_correct = False
539 
540  if options.git:
541  files = git_modified_files()
542  style_is_correct = indent_files(files,
543  diff=options.diff,
544  debug=options.debug,
545  level=options.level,
546  inplace=options.in_place)
547  elif options.file != '':
548  file = options.file
549  if not os.path.exists(file) or \
550  not os.path.isfile(file):
551  print('file %s does not exist' % file)
552  sys.exit(1)
553  style_is_correct = indent_files([file],
554  diff=options.diff,
555  debug=options.debug,
556  level=options.level,
557  inplace=options.in_place)
558 
559  if not style_is_correct:
560  sys.exit(1)
561  sys.exit(0)
562 
563 if __name__ == '__main__':
564  try:
565  run_as_main()
566  except Exception as e:
567  sys.stderr.write(str(e) + '\n')
568  sys.exit(1)
PatchChunk class.
Definition: check-style.py:230
def dst(self)
Get destination lines.
Definition: check-style.py:273
def src(self)
Get source lines.
Definition: check-style.py:263
def dst_len(self)
Get number of destinaton lines.
Definition: check-style.py:289
def src_start(self)
Source start function.
Definition: check-style.py:250
def write(self, f)
Write lines to file.
Definition: check-style.py:295
__src_pos
source position
Definition: check-style.py:248
__lines
list of lines
Definition: check-style.py:247
def __init__(self, src_pos, dst_pos)
Initializer.
Definition: check-style.py:241
def add_line(self, line)
Add line function.
Definition: check-style.py:256
def src_len(self)
Get number of source lines.
Definition: check-style.py:283
__dst_pos
destination position
Definition: check-style.py:249
PatchChunkLine class.
Definition: check-style.py:145
def __init__(self)
Initializer.
Definition: check-style.py:159
def set_both(self, line)
Set both.
Definition: check-style.py:181
def line(self)
Get line.
Definition: check-style.py:196
def write(self, f)
Write to file.
Definition: check-style.py:214
def is_src(self)
Is source.
Definition: check-style.py:202
def set_dst(self, line)
Set destination.
Definition: check-style.py:173
def set_src(self, line)
Set source.
Definition: check-style.py:165
def is_dst(self)
Is destination.
Definition: check-style.py:208
def append_to_line(self, s)
Append to line.
Definition: check-style.py:189
Patch class.
Definition: check-style.py:307
def add_chunk(self, chunk)
Add chunk.
Definition: check-style.py:321
def chunks(self)
Get the chunks.
Definition: check-style.py:328
def set_src(self, src)
Set source.
Definition: check-style.py:334
__dst
destination
Definition: check-style.py:319
def set_dst(self, dst)
Set destination.
Definition: check-style.py:341
def apply(self, filename)
Apply function.
Definition: check-style.py:348
def write(self, f)
Write to file.
Definition: check-style.py:356
def __init__(self)
Initializer.
Definition: check-style.py:314
def parse_patchset(generator)
Definition: check-style.py:367
def uncrustify_config_file(level)
Definition: check-style.py:33
def remove_trailing_whitespace_changes(patch_generator)
Definition: check-style.py:413
def indent(source, debug, level)
Definition: check-style.py:432
def copy_file(filename)
Definition: check-style.py:25
def run_as_main()
Definition: check-style.py:521
def indent_files(files, diff=False, debug=False, level=0, inplace=False)
Definition: check-style.py:489
def git_modified_files()
Definition: check-style.py:13