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