1#!/usr/local/bin/python3.8 2 3"""Use clang-format and autopep8 when available to clean up the listed source 4 files.""" 5 6from __future__ import print_function 7from optparse import OptionParser 8import subprocess 9import os 10import sys 11import multiprocessing 12try: 13 from queue import Queue # python3 14except ImportError: 15 from Queue import Queue # python2 16from threading import Thread 17try: 18 from shutil import which # python3.3 or later 19except ImportError: 20 from distutils.spawn import find_executable as which 21 22sys.path.append(os.path.split(sys.argv[0])) 23import python_tools 24from python_tools.reindent import Reindenter 25 26 27parser = OptionParser(usage="%prog [options] [FILENAME ...]", 28 description="""Reformat the given C++ and Python files 29(using the clang-format tool if available and 30reindent.py, respectively). If the --all option is given, reformat all such 31files under the current directory. 32 33If the autopep8 tool is also available, it can be used instead of reindent.py 34by giving the -a option. autopep8 is much more aggressive than reindent.py 35and will fix other issues, such as use of old-style Python syntax. 36""") 37parser.add_option("-c", "--clang-format", dest="clang_format", 38 default="auto", metavar="EXE", 39 help="The clang-format command.") 40parser.add_option("-a", dest="use_ap", action="store_true", default=False, 41 help="Use autopep8 rather than reindent.py for " 42 "Python files.") 43parser.add_option("--all", dest="all_files", action="store_true", 44 default=False, 45 help="Reformat all files under current directory") 46parser.add_option("--autopep8", dest="autopep8", 47 default="auto", metavar="EXE", 48 help="The autopep8 command.") 49parser.add_option("-e", "--exclude", dest="exclude", 50 default="eigen3:config_templates", metavar="DIRS", 51 help="Colon-separated list of dirnames to ignore.") 52parser.add_option("-v", "--verbose", dest="verbose", action="store_true", 53 default=False, 54 help="Print extra info.") 55(options, args) = parser.parse_args() 56if not args and not options.all_files: 57 parser.error("No files selected") 58 59# clang-format-3.4", 60# autopep8 61 62# search for executables 63if options.clang_format == "auto": 64 options.clang_format = None 65 for name in ["clang-format-3.4", "clang-format"]: 66 if which(name): 67 options.clang_format = name 68 break 69if options.autopep8 == "auto": 70 options.autopep8 = None 71 for name in ["autopep8"]: 72 if which(name): 73 options.autopep8 = name 74 break 75 76exclude = options.exclude.split(":") 77 78error = None 79 80 81class _Worker(Thread): 82 83 """Thread executing tasks from a given tasks queue""" 84 85 def __init__(self, tasks): 86 Thread.__init__(self) 87 self.tasks = tasks 88 self.daemon = True 89 self.start() 90 91 def run(self): 92 while True: 93 func, args, kargs = self.tasks.get() 94 try: 95 func(*args, **kargs) 96 except Exception as e: 97 print(e) 98 self.tasks.task_done() 99 100 101class ThreadPool: 102 103 """Pool of threads consuming tasks from a queue""" 104 105 def __init__(self, num_threads=-1): 106 if num_threads == -1: 107 num_threads = 2 * multiprocessing.cpu_count() 108 # print "Creating thread pool with", num_threads 109 self.tasks = Queue(-1) 110 for _ in range(num_threads): 111 _Worker(self.tasks) 112 113 def add_task(self, func, *args, **kargs): 114 """Add a task to the queue""" 115 # func(*args, **kargs) 116 self.tasks.put((func, args, kargs)) 117 118 def wait_completion(self): 119 """Wait for completion of all the tasks in the queue""" 120 self.tasks.join() 121 return error 122 123 124def _do_get_files(glb, cur): 125 matches = [] 126 for n in os.listdir(cur): 127 if n in exclude: 128 continue 129 name = os.path.join(cur, n) 130 if os.path.isdir(name): 131 if not os.path.exists(os.path.join(name, ".git")): 132 matches += _do_get_files(glb, name) 133 elif name.endswith(glb): 134 matches.append(name) 135 return matches 136 137 138def _get_files(glb): 139 match = [] 140 if len(args) == 0: 141 match = _do_get_files(glb, ".") 142 else: 143 for a in args: 144 if os.path.isdir(a): 145 match += _do_get_files(glb, a) 146 elif a.endswith(glb): 147 match.append(a) 148 return match 149 150 151def _run(cmd): 152 # print " ".join(cmd) 153 pro = subprocess.Popen(cmd, stderr=subprocess.PIPE, 154 stdout=subprocess.PIPE, universal_newlines=True) 155 output, error = pro.communicate() 156 if pro.returncode != 0: 157 print(" ".join(cmd)) 158 raise RuntimeError("error running " + error) 159 return output 160 161 162def clean_cpp(path): 163 # skip code that isn't ours 164 if "dependency" in path or "/eigen3/" in path: 165 return 166 if options.clang_format: 167 contents = _run([options.clang_format, "--style=Google", path]) 168 else: 169 contents = open(path, "r").read() 170 contents = contents.replace("% template", "%template") 171 python_tools.rewrite(path, contents) 172 173 174def clean_py(path): 175 if options.use_ap and options.autopep8: 176 contents = _run([options.autopep8, "--aggressive", "--aggressive", 177 path]) 178 else: 179 r = Reindenter(open(path)) 180 r.run() 181 contents = ''.join(r.after) 182 if contents.find("# \\example") != -1: 183 contents = "#" + contents 184 python_tools.rewrite(path, contents) 185 186 187def main(): 188 if options.verbose: 189 if options.autopep8 is None: 190 print("autopep8 not found") 191 else: 192 print("autopep8 is `%s`" % options.autopep8) 193 if options.clang_format is None: 194 print("clang-format not found") 195 else: 196 print("clang-format is `%s`" % options.clang_format) 197 198 tp = ThreadPool() 199 200 if args: 201 for f in args: 202 if f.endswith(".py"): 203 tp.add_task(clean_py, f) 204 elif f.endswith(".h") or f.endswith(".cpp"): 205 tp.add_task(clean_cpp, f) 206 elif options.all_files: 207 for f in _get_files(".py"): 208 tp.add_task(clean_py, f) 209 for f in _get_files(".h") + _get_files(".cpp"): 210 tp.add_task(clean_cpp, f) 211 tp.wait_completion() 212 213 214if __name__ == '__main__': 215 main() 216