1#!/usr/bin/env python
2
3"""\
4py.cleanup [PATH] ...
5
6Delete typical python development related files recursively under the specified PATH (which defaults to the current working directory). Don't follow links and don't recurse into directories with a dot.  Optionally remove setup.py related files and empty
7directories.
8
9"""
10import py
11import sys, subprocess
12
13def main():
14    parser = py.std.optparse.OptionParser(usage=__doc__)
15    parser.add_option("-e", metavar="ENDING",
16        dest="endings", default=[".pyc", "$py.class"], action="append",
17        help=("(multi) recursively remove files with the given ending."
18             " '.pyc' and '$py.class' are in the default list."))
19    parser.add_option("-d", action="store_true", dest="removedir",
20                      help="remove empty directories.")
21    parser.add_option("-p", action="store_true", dest="pycache",
22                      help="remove __pycache__ directories.")
23    parser.add_option("-s", action="store_true", dest="setup",
24                      help="remove 'build' and 'dist' directories next to setup.py files")
25    parser.add_option("-a", action="store_true", dest="all",
26                      help="synonym for '-p -S -d -e pip-log.txt'")
27    parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
28                      help="don't print each removed filename on output")
29    parser.add_option("-n", "--dryrun", dest="dryrun", default=False,
30        action="store_true",
31        help="don't actually delete but display would-be-removed filenames.")
32    (options, args) = parser.parse_args()
33
34    Cleanup(options, args).main()
35
36class Cleanup:
37    def __init__(self, options, args):
38        if not args:
39            args = ["."]
40        self.options = options
41        self.args = [py.path.local(x) for x in args]
42        if options.all:
43            options.setup = True
44            options.removedir = True
45            options.pycache = True
46            options.endings.append("pip-log.txt")
47
48    def main(self):
49        if self.options.setup:
50            for arg in self.args:
51                self.setupclean(arg)
52
53        for path in self.args:
54            py.builtin.print_("cleaning path", path,
55                "of extensions", self.options.endings)
56            for x in path.visit(self.shouldremove, self.recursedir):
57                self.remove(x)
58        if self.options.removedir:
59            for x in path.visit(lambda x: x.check(dir=1), self.recursedir):
60                if not x.listdir():
61                    self.remove(x)
62
63    def shouldremove(self, p):
64        if p.check(file=1):
65            for ending in self.options.endings:
66                if p.basename.endswith(ending):
67                    return True
68            return False
69        if self.options.pycache and p.basename == "__pycache__":
70            return True
71
72    def recursedir(self, path):
73        return path.check(dotfile=0, link=0)
74
75    def remove(self, path):
76        if not path.check():
77            return
78        if self.options.dryrun:
79            py.builtin.print_("would remove", path)
80        else:
81            if not self.options.quiet:
82                py.builtin.print_("removing", path)
83            path.remove()
84
85    def XXXcallsetup(self, setup, *args):
86        old = setup.dirpath().chdir()
87        try:
88            subprocess.call([sys.executable, str(setup)] + list(args))
89        finally:
90            old.chdir()
91
92    def setupclean(self, path):
93        for x in path.visit("setup.py", self.recursedir):
94            basepath = x.dirpath()
95            self.remove(basepath / "build")
96            self.remove(basepath / "dist")
97