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