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, error) = 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  [tmp,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  [tmp,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  @return none
163  """
164  self.__type = 0
165  self.__line = ''
166  def set_src(self,line):
167  """! Set source
168  @param self The current class
169  @param line source line
170  @return none
171  """
172  self.__type = self.SRC
173  self.__line = line
174  def set_dst(self,line):
175  """! Set destination
176  @param self The current class
177  @param line destination line
178  @return none
179  """
180  self.__type = self.DST
181  self.__line = line
182  def set_both(self,line):
183  """! Set both
184  @param self The current class
185  @param line
186  @return none
187  """
188  self.__type = self.BOTH
189  self.__line = line
190  def append_to_line(self, s):
191  """! Append to line
192  @param self The current class
193  @param s line to append
194  @return none
195  """
196  self.__line = self.__line + s
197  def line(self):
198  """! Get line
199  @param self The current class
200  @return line
201  """
202  return self.__line
203  def is_src(self):
204  """! Is source
205  @param self The current class
206  @return true if type is source
207  """
208  return self.__type == self.SRC or self.__type == self.BOTH
209  def is_dst(self):
210  """! Is destination
211  @param self The current class
212  @return true if type is destination
213  """
214  return self.__type == self.DST or self.__type == self.BOTH
215  def write(self, f):
216  """! Write to file
217  @param self The current class
218  @param f file
219  @return exception if invalid type
220  """
221  if self.__type == self.SRC:
222  f.write('-%s\n' % self.__line)
223  elif self.__type == self.DST:
224  f.write('+%s\n' % self.__line)
225  elif self.__type == self.BOTH:
226  f.write(' %s\n' % self.__line)
227  else:
228  raise Exception('invalid patch')
229 
230 
232 
242  def __init__(self, src_pos, dst_pos):
243  """! Initializer
244  @param self: this object
245  @param src_pos: source position
246  @param dst_pos: destination position
247  @return none
248  """
249  self.__lines = []
250  self.__src_pos = int(src_pos)
251  self.__dst_pos = int(dst_pos)
252  def src_start(self):
253  """! Source start function
254  @param self this object
255  @return source position
256  """
257  return self.__src_pos
258  def add_line(self,line):
259  """! Add line function
260  @param self The current class
261  @param line line to add
262  @return none
263  """
264  self.__lines.append(line)
265  def src(self):
266  """! Get source lines
267  @param self The current class
268  @return the source lines
269  """
270  src = []
271  for line in self.__lines:
272  if line.is_src():
273  src.append(line)
274  return src
275  def dst(self):
276  """! Get destination lines
277  @param self The current class
278  @return the destination lines
279  """
280  dst = []
281  for line in self.__lines:
282  if line.is_dst():
283  dst.append(line)
284  return dst
285  def src_len(self):
286  """! Get number of source lines
287  @param self The current class
288  @return number of source lines
289  """
290  return len(self.src())
291  def dst_len(self):
292  """! Get number of destinaton lines
293  @param self The current class
294  @return number of destination lines
295  """
296  return len(self.dst())
297  def write(self,f):
298  """! Write lines to file
299  @param self The current class
300  @param f: file to write to
301  @return none
302  """
303  f.write('@@ -%d,%d +%d,%d @@\n' % (self.__src_pos, self.src_len(),
304  self.__dst_pos, self.dst_len()))
305  for line in self.__lines:
306  line.write(f)
307 
308 
309 class Patch:
310 
316  def __init__(self):
317  """! Initializer
318  @param self The current class
319  @return none
320  """
321  self.__src = ''
322  self.__dst = ''
323  self.__chunks = []
324  def add_chunk(self, chunk):
325  """! Add chunk
326  @param self this object
327  @param chunk chunk
328  @return none
329  """
330  self.__chunks.append(chunk)
331  def chunks(self):
332  """! Get the chunks
333  @param self The current class
334  @return the chunks
335  """
336  return self.__chunks
337  def set_src(self,src):
338  """! Set source
339  @param self this object
340  @param src source
341  @return none
342  """
343  self.__src = src
344  def set_dst(self,dst):
345  """! Set destination
346  @param self this object
347  @param dst destintion
348  @return none
349  """
350  self.__dst = dst
351  def apply(self,filename):
352  """! Apply function
353  @param self The current class
354  @param filename file name
355  @return none
356  """
357  # XXX: not implemented
358  return
359  def write(self,f):
360  """! Write to file
361  @param self The current class
362  @param f the file
363  @return none
364  """
365  f.write('--- %s\n' % self.__src )
366  f.write('+++ %s\n' % self.__dst )
367  for chunk in self.__chunks:
368  chunk.write(f)
369 
370 def parse_patchset(generator):
371  src_file = re.compile('^--- (.*)$')
372  dst_file = re.compile('^\+\+\+ (.*)$')
373  chunk_start = re.compile('^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@')
374  src = re.compile('^-(.*)$')
375  dst = re.compile('^\+(.*)$')
376  both = re.compile('^ (.*)$')
377  patchset = []
378  current_patch = None
379  for line in generator:
380  m = src_file.search(line)
381  if m is not None:
382  current_patch = Patch()
383  patchset.append(current_patch)
384  current_patch.set_src(m.group(1))
385  continue
386  m = dst_file.search(line)
387  if m is not None:
388  current_patch.set_dst(m.group(1))
389  continue
390  m = chunk_start.search(line)
391  if m is not None:
392  current_chunk = PatchChunk(m.group(1), m.group(3))
393  current_patch.add_chunk(current_chunk)
394  continue
395  m = src.search(line)
396  if m is not None:
397  l = PatchChunkLine()
398  l.set_src(m.group(1))
399  current_chunk.add_line(l)
400  continue
401  m = dst.search(line)
402  if m is not None:
403  l = PatchChunkLine()
404  l.set_dst(m.group(1))
405  current_chunk.add_line(l)
406  continue
407  m = both.search(line)
408  if m is not None:
409  l = PatchChunkLine()
410  l.set_both(m.group(1))
411  current_chunk.add_line(l)
412  continue
413  raise Exception()
414  return patchset
415 
417  whitespace = re.compile('^(.*)([ \t]+)$')
418  patchset = parse_patchset(patch_generator)
419  for patch in patchset:
420  for chunk in patch.chunks():
421  src = chunk.src()
422  dst = chunk.dst()
423  try:
424  for i in range(0,len(src)):
425  s = src[i]
426  d = dst[i]
427  m = whitespace.search(s.line())
428  if m is not None and m.group(1) == d.line():
429  d.append_to_line(m.group(2))
430  except:
431  return patchset
432  return patchset
433 
434 
435 def indent(source, debug, level):
436  output = tempfile.mkstemp()[1]
437  # apply uncrustify
438  cfg = uncrustify_config_file(level)
439  if debug:
440  sys.stderr.write('original file=' + source + '\n')
441  sys.stderr.write('uncrustify config file=' + cfg + '\n')
442  sys.stderr.write('temporary file=' + output + '\n')
443  try:
444  uncrust = subprocess.Popen(['uncrustify', '-c', cfg, '-f', source, '-o', output],
445  stdin = subprocess.PIPE,
446  stdout = subprocess.PIPE,
447  stderr = subprocess.PIPE,
448  universal_newlines = True)
449  (out, err) = uncrust.communicate('')
450  if debug:
451  sys.stderr.write(out)
452  sys.stderr.write(err)
453  except OSError:
454  raise Exception ('uncrustify not installed')
455  # generate a diff file
456  with open(source, 'r') as src, open(output, 'r') as dst:
457  diff = difflib.unified_diff(src.readlines(), dst.readlines(),
458  fromfile=source, tofile=output)
459  if debug:
460  initial_diff = tempfile.mkstemp()[1]
461  sys.stderr.write('initial diff file=' + initial_diff + '\n')
462  with open(initial_diff, 'w') as tmp:
463  tmp.writelines(diff)
464  final_diff = tempfile.mkstemp()[1]
465  if level < 3:
466  patchset = remove_trailing_whitespace_changes(diff)
467  if len(patchset) != 0:
468  with open(final_diff, 'w') as dst:
469  patchset[0].write(dst)
470  else:
471  with open(final_diff, 'w') as dst:
472  dst.writelines(diff)
473 
474 
475  # apply diff file
476  if debug:
477  sys.stderr.write('final diff file=' + final_diff + '\n')
478  shutil.copyfile(source,output)
479  patch = subprocess.Popen(['patch', '-p1', '-i', final_diff, output],
480  stdin = subprocess.PIPE,
481  stdout = subprocess.PIPE,
482  stderr = subprocess.PIPE,
483  universal_newlines = True)
484  (out, err) = patch.communicate('')
485  if debug:
486  sys.stderr.write(out)
487  sys.stderr.write(err)
488  return output
489 
490 
491 
492 def indent_files(files, diff=False, debug=False, level=0, inplace=False):
493  output = []
494  for f in files:
495  dst = indent(f, debug=debug, level=level)
496  output.append([f,dst])
497 
498  # First, copy to inplace
499  if inplace:
500  for src,dst in output:
501  shutil.copyfile(dst,src)
502  return True
503 
504  # now, compare
505  failed = []
506  for src,dst in output:
507  if filecmp.cmp(src,dst) == 0:
508  failed.append([src, dst])
509  if len(failed) > 0:
510  if not diff:
511  print('Found %u badly indented files:' % len(failed))
512  for src,dst in failed:
513  print(' ' + src)
514  else:
515  for src,dst in failed:
516  with open(src, 'r') as f_src, open(dst, 'r') as f_dst:
517  s = f_src.readlines()
518  d = f_dst.readlines()
519  for line in difflib.unified_diff(s, d, fromfile=src, tofile=dst):
520  sys.stdout.write(line)
521  return False
522  return True
523 
525  parser = optparse.OptionParser()
526  parser.add_option('--debug', action='store_true', dest='debug', default=False,
527  help='Output some debugging information')
528  parser.add_option('-l', '--level', type='int', dest='level', default=0,
529  help="Level of style conformance: higher levels include all lower levels. "
530  "level=0: re-indent only. level=1: add extra spaces. level=2: insert extra newlines and "
531  "extra braces around single-line statements. level=3: remove all trailing spaces")
532  parser.add_option('--check-git', action='store_true', dest='git', default=False,
533  help="Get the list of files to check from Git\'s list of modified and added files")
534  parser.add_option('-f', '--check-file', action='store', dest='file', default='',
535  help="Check a single file")
536  parser.add_option('--diff', action='store_true', dest='diff', default=False,
537  help="Generate a diff on stdout of the indented files")
538  parser.add_option('-i', '--in-place', action='store_true', dest='in_place', default=False,
539  help="Indent the input files in-place")
540  (options,args) = parser.parse_args()
541  debug = options.debug
542  style_is_correct = False;
543 
544  if options.git:
545  files = git_modified_files()
546  style_is_correct = indent_files(files,
547  diff=options.diff,
548  debug=options.debug,
549  level=options.level,
550  inplace=options.in_place)
551  elif options.file != '':
552  file = options.file
553  if not os.path.exists(file) or \
554  not os.path.isfile(file):
555  print('file %s does not exist' % file)
556  sys.exit(1)
557  style_is_correct = indent_files([file],
558  diff=options.diff,
559  debug=options.debug,
560  level=options.level,
561  inplace=options.in_place)
562 
563  if not style_is_correct:
564  sys.exit(1)
565  sys.exit(0)
566 
567 if __name__ == '__main__':
568  try:
569  run_as_main()
570  except Exception as e:
571  sys.stderr.write(str(e) + '\n')
572  sys.exit(1)
def append_to_line(self, s)
Append to line.
Definition: check-style.py:190
def set_src(self, src)
Set source.
Definition: check-style.py:337
PatchChunkLine class.
Definition: check-style.py:145
def apply(self, filename)
Apply function.
Definition: check-style.py:351
def set_both(self, line)
Set both.
Definition: check-style.py:182
Patch class.
Definition: check-style.py:309
def chunks(self)
Get the chunks.
Definition: check-style.py:331
def dst(self)
Get destination lines.
Definition: check-style.py:275
def line(self)
Get line.
Definition: check-style.py:197
def indent(source, debug, level)
Definition: check-style.py:435
__dst
destination
Definition: check-style.py:322
def __init__(self, src_pos, dst_pos)
Initializer.
Definition: check-style.py:242
def add_line(self, line)
Add line function.
Definition: check-style.py:258
def copy_file(filename)
Definition: check-style.py:25
def set_src(self, line)
Set source.
Definition: check-style.py:166
def src(self)
Get source lines.
Definition: check-style.py:265
def write(self, f)
Write to file.
Definition: check-style.py:359
__dst_pos
destination position
Definition: check-style.py:251
def is_src(self)
Is source.
Definition: check-style.py:203
def is_dst(self)
Is destination.
Definition: check-style.py:209
def set_dst(self, dst)
Set destination.
Definition: check-style.py:344
__src_pos
source position
Definition: check-style.py:250
def remove_trailing_whitespace_changes(patch_generator)
Definition: check-style.py:416
def git_modified_files()
Definition: check-style.py:13
def uncrustify_config_file(level)
Definition: check-style.py:33
def dst_len(self)
Get number of destinaton lines.
Definition: check-style.py:291
def run_as_main()
Definition: check-style.py:524
int DST
Destination.
Definition: check-style.py:155
def __init__(self)
Initializer.
Definition: check-style.py:316
def write(self, f)
Write to file.
Definition: check-style.py:215
def src_start(self)
Source start function.
Definition: check-style.py:252
def set_dst(self, line)
Set destination.
Definition: check-style.py:174
def src_len(self)
Get number of source lines.
Definition: check-style.py:285
def parse_patchset(generator)
Definition: check-style.py:370
__lines
list of lines
Definition: check-style.py:249
def write(self, f)
Write lines to file.
Definition: check-style.py:297
def add_chunk(self, chunk)
Add chunk.
Definition: check-style.py:324
PatchChunk class.
Definition: check-style.py:231
def __init__(self)
Initializer.
Definition: check-style.py:159
def indent_files(files, diff=False, debug=False, level=0, inplace=False)
Definition: check-style.py:492