/common/fiximports.py

https://gitlab.com/murder187ss/buildbot · Python · 175 lines · 126 code · 25 blank · 24 comment · 40 complexity · addfa169825177cfd7aad386358d4ebc MD5 · raw file

  1. #!/usr/bin/env python
  2. '''Check and sort import statement from a python file '''
  3. import re
  4. import sys
  5. class FixImports(object):
  6. '''
  7. I can be used to check and sort import statement of a python file
  8. Please use sortImportGroups() method
  9. '''
  10. _regexImport = re.compile(r"^import\s+(.*)")
  11. _regexFromImport = re.compile(r"^from\s+([a-zA-Z0-9\._]+)\s+import\s+(.*)$")
  12. _regexFromFutureImport = re.compile(r"^from\s+__future__\s+import\s+(.*)$")
  13. def printErrorMsg(self, filename, lineNb, errorMessage):
  14. ''' I print the error message following pylint convention'''
  15. print ("%(filename)s:%(line_nb)s: %(error_msg)s" %
  16. dict(filename=filename,
  17. line_nb=lineNb,
  18. error_msg=errorMessage))
  19. def isImportLine(self, line):
  20. '''I return True is the given line is an import statement, False otherwize'''
  21. return self._regexImport.match(line) or self._regexFromImport.match(line)
  22. def isBadLineFixable(self, line):
  23. '''I return True is the given line is an import line than I know how to split'''
  24. if self.isImportLine(line) and '(' not in line:
  25. return True
  26. return False
  27. def analyzeLine(self, filename, line, lineNb):
  28. '''I look at the line and print all error I find'''
  29. res = True
  30. if self.isImportLine(line):
  31. if ',' in line:
  32. self.printErrorMsg(filename, lineNb,
  33. "multiple modules imported on one line - will fix")
  34. res = False
  35. if '\\' in line:
  36. self.printErrorMsg(filename, lineNb,
  37. "line-continuation character found - will fix.")
  38. res = False
  39. # these two don't occur in the Buildbot codebase, so we don't try to
  40. # fix them
  41. if ';' in line:
  42. self.printErrorMsg(filename, lineNb,
  43. "multiple import statement on one line. "
  44. "Put each import on its own line.")
  45. res = False
  46. if '(' in line:
  47. self.printErrorMsg(filename, lineNb,
  48. "parenthesis character found. "
  49. "Please import each module on a single line")
  50. res = False
  51. return res
  52. def importOrder(self, line):
  53. '''
  54. I define how import lines should be sorted
  55. return a tuple of order criterias sorted be importance
  56. '''
  57. ret = ("__future__" not in line, # always put __future__ import first
  58. self._regexFromImport.match(line) is not None, # import before from import
  59. line, # then lexicographic order
  60. )
  61. return ret
  62. def sortImportGroups(self, filename, data=None):
  63. '''
  64. I perform the analysis of the given file, print the error I find and try to split and
  65. sort the import statement
  66. '''
  67. lines = data.split("\n")
  68. res = True
  69. for cur_line_nb, line in enumerate(lines):
  70. if not self.analyzeLine(filename, line, cur_line_nb):
  71. if not self.isBadLineFixable(line):
  72. res = False
  73. if not res:
  74. return False, data
  75. # First split the import we can split
  76. newlines = []
  77. self.groups = []
  78. self.group_start = None
  79. def maybeEndGroup():
  80. if self.group_start is not None:
  81. self.groups.append((self.group_start, len(newlines)))
  82. self.group_start = None
  83. iter = lines.__iter__()
  84. while True:
  85. try:
  86. line = iter.next()
  87. except StopIteration:
  88. break
  89. if self.isImportLine(line):
  90. # join any continuation lines (\\)
  91. while line[-1] == '\\':
  92. line = line[:-1] + iter.next()
  93. if self.group_start is None:
  94. self.group_start = len(newlines)
  95. if self.isBadLineFixable(line):
  96. match = self._regexFromImport.match(line)
  97. if match:
  98. module = match.group(1)
  99. imports = [s.strip() for s in match.group(2).split(",")]
  100. for imp in imports:
  101. newlines.append("from %s import %s" % (module, imp))
  102. continue
  103. else:
  104. maybeEndGroup()
  105. newlines.append(line)
  106. maybeEndGroup()
  107. lines = newlines
  108. for start, end in self.groups:
  109. lines[start:end] = sorted(lines[start:end], key=self.importOrder)
  110. # reiterate line by line to split mixed groups
  111. splitted_groups_lines = []
  112. prev_import_line_type = ""
  113. for line in lines:
  114. if not line.strip() or not self.isImportLine(line):
  115. splitted_groups_lines.append(line)
  116. prev_import_line_type = ""
  117. else:
  118. import_match = self._regexImport.match(line)
  119. from_match = self._regexFromImport.match(line)
  120. current_line_type = None
  121. if import_match is not None:
  122. module = import_match
  123. current_line_type = "import"
  124. elif from_match is not None:
  125. module = from_match
  126. current_line_type = "from"
  127. assert(current_line_type)
  128. if prev_import_line_type and current_line_type != prev_import_line_type:
  129. splitted_groups_lines.append("")
  130. prev_import_line_type = current_line_type
  131. splitted_groups_lines.append(line)
  132. return True, "\n".join(splitted_groups_lines)
  133. def main():
  134. '''I am the main method'''
  135. if len(sys.argv) != 2:
  136. print "usage: %s <python file>" % (sys.argv[0])
  137. sys.exit(1)
  138. filename = sys.argv[1]
  139. with open(filename, 'r') as filedesc:
  140. data = filedesc.read()
  141. res, content = FixImports().sortImportGroups(filename, data)
  142. if not res:
  143. sys.exit(1)
  144. with open(filename, 'w') as filedesc:
  145. filedesc.write(content)
  146. if data != content:
  147. print "import successfully reordered for file: %s" % (filename)
  148. sys.exit(0)
  149. if __name__ == "__main__":
  150. main()