lint.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env python3
  2. import os
  3. import re
  4. import shutil
  5. import subprocess
  6. import multiprocessing
  7. from flipper.app import App
  8. SOURCE_CODE_FILE_EXTENSIONS = [".h", ".c", ".cpp", ".cxx", ".hpp"]
  9. SOURCE_CODE_FILE_PATTERN = r"^[0-9A-Za-z_]+\.[a-z]+$"
  10. SOURCE_CODE_DIR_PATTERN = r"^[0-9A-Za-z_]+$"
  11. class Main(App):
  12. def init(self):
  13. self.subparsers = self.parser.add_subparsers(help="sub-command help")
  14. # generate
  15. self.parser_check = self.subparsers.add_parser(
  16. "check", help="Check source code format and file names"
  17. )
  18. self.parser_check.add_argument("input", nargs="+")
  19. self.parser_check.set_defaults(func=self.check)
  20. # merge
  21. self.parser_format = self.subparsers.add_parser(
  22. "format", help="Format source code and fix file names"
  23. )
  24. self.parser_format.add_argument(
  25. "input",
  26. nargs="+",
  27. )
  28. self.parser_format.set_defaults(func=self.format)
  29. def _check_folders(self, folders: list):
  30. show_message = False
  31. pattern = re.compile(SOURCE_CODE_DIR_PATTERN)
  32. for folder in folders:
  33. for dirpath, dirnames, filenames in os.walk(folder):
  34. for dirname in dirnames:
  35. if not pattern.match(dirname):
  36. to_fix = os.path.join(dirpath, dirname)
  37. self.logger.warning(f"Found incorrectly named folder {to_fix}")
  38. show_message = True
  39. if show_message:
  40. self.logger.warning(
  41. f"Folders are not renamed automatically, please fix it by yourself"
  42. )
  43. def _find_sources(self, folders: list):
  44. output = []
  45. for folder in folders:
  46. for dirpath, dirnames, filenames in os.walk(folder):
  47. for filename in filenames:
  48. ext = os.path.splitext(filename.lower())[1]
  49. if not ext in SOURCE_CODE_FILE_EXTENSIONS:
  50. continue
  51. output.append(os.path.join(dirpath, filename))
  52. return output
  53. @staticmethod
  54. def _format_source(task):
  55. try:
  56. subprocess.check_call(task)
  57. return True
  58. except subprocess.CalledProcessError as e:
  59. return False
  60. def _format_sources(self, sources: list, dry_run: bool = False):
  61. args = ["clang-format", "--Werror", "--style=file", "-i"]
  62. if dry_run:
  63. args.append("--dry-run")
  64. files_per_task = 69
  65. tasks = []
  66. while len(sources) > 0:
  67. tasks.append(args + sources[:files_per_task])
  68. sources = sources[files_per_task:]
  69. pool = multiprocessing.Pool()
  70. results = pool.map(self._format_source, tasks)
  71. return all(results)
  72. def _fix_filename(self, filename: str):
  73. return filename.replace("-", "_")
  74. def _replace_occurance(self, sources: list, old: str, new: str):
  75. old = old.encode()
  76. new = new.encode()
  77. for source in sources:
  78. content = open(source, "rb").read()
  79. if content.count(old) > 0:
  80. self.logger.info(f"Replacing {old} with {new} in {source}")
  81. content = content.replace(old, new)
  82. open(source, "wb").write(content)
  83. def _apply_file_naming_convention(self, sources: list, dry_run: bool = False):
  84. pattern = re.compile(SOURCE_CODE_FILE_PATTERN)
  85. good = []
  86. bad = []
  87. # Check sources for invalid filesname
  88. for source in sources:
  89. basename = os.path.basename(source)
  90. if not pattern.match(basename):
  91. new_basename = self._fix_filename(basename)
  92. if not pattern.match(new_basename):
  93. self.logger.error(f"Unable to fix name for {basename}")
  94. return False
  95. bad.append((source, basename, new_basename))
  96. else:
  97. good.append(source)
  98. # Notify about errors or replace all occurances
  99. if dry_run:
  100. if len(bad) > 0:
  101. self.logger.error(f"Found {len(bad)} incorrectly named files")
  102. return False
  103. else:
  104. # Replace occurances in text files
  105. for source, old, new in bad:
  106. self._replace_occurance(sources, old, new)
  107. # Rename files
  108. for source, old, new in bad:
  109. shutil.move(source, source.replace(old, new))
  110. return True
  111. def check(self):
  112. result = 0
  113. sources = self._find_sources(self.args.input)
  114. if not self._format_sources(sources, dry_run=True):
  115. result |= 0b01
  116. if not self._apply_file_naming_convention(sources, dry_run=True):
  117. result |= 0b10
  118. self._check_folders(self.args.input)
  119. return result
  120. def format(self):
  121. result = 0
  122. sources = self._find_sources(self.args.input)
  123. if not self._format_sources(sources):
  124. result |= 0b01
  125. if not self._apply_file_naming_convention(sources):
  126. result |= 0b10
  127. self._check_folders(self.args.input)
  128. return result
  129. if __name__ == "__main__":
  130. Main()()