~launchpad-pqm/launchpad/devel

9492.1.1 by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought
1
#!/usr/bin/python
2
#
3
# Copyright (C) 2009 - Curtis Hovey <sinzui.is at verizon.net>
4
# This software is licensed under the GNU General Public License version 2.
5
#
6
# It comes from the Gedit Developer Plugins project (launchpad.net/gdp); see
7
# http://bazaar.launchpad.net/~sinzui/gdp/trunk/files/head%3A/plugins/gdp/ &
8
# http://bazaar.launchpad.net/%7Esinzui/gdp/trunk/annotate/head%3A/COPYING.
9
10
"""Find in files and replace strings in many files."""
11
12
import mimetypes
13
import os
14
import re
15
import sys
16
17
from optparse import OptionParser
18
19
20
mimetypes.init()
21
22
23
def find_matches(root_dir, file_pattern, match_pattern, substitution=None):
24
    """Iterate a summary of matching lines in a file."""
25
    match_re = re.compile(match_pattern)
26
    for file_path in find_files(root_dir, file_pattern=file_pattern):
27
        summary = extract_match(file_path, match_re, substitution=substitution)
28
        if summary:
29
            yield summary
30
31
32
def find_files(root_dir, skip_dir_pattern='^[.]', file_pattern='.*'):
33
    """Iterate the matching files below a directory."""
34
    skip_dir_re = re.compile(r'^.*%s' % skip_dir_pattern)
35
    file_re = re.compile(r'^.*%s' % file_pattern)
36
    for path, subdirs, files in os.walk(root_dir):
37
        subdirs[:] = [dir_ for dir_ in subdirs
38
                      if skip_dir_re.match(dir_) is None]
39
        for file_ in files:
40
            file_path = os.path.join(path, file_)
41
            if os.path.islink(file_path):
42
                continue
43
            mime_type, encoding = mimetypes.guess_type(file_)
44
            if mime_type is None or 'text/' in mime_type:
45
                if file_re.match(file_path) is not None:
46
                    yield file_path
47
48
49
def extract_match(file_path, match_re, substitution=None):
50
    """Return a summary of matches in a file."""
51
    lines = []
52
    content = []
53
    match = None
54
    file_ = open(file_path, 'r')
55
    try:
56
        for lineno, line in enumerate(file_):
57
            match = match_re.search(line)
58
            if match:
59
                lines.append(
60
                    {'lineno' : lineno + 1, 'text' : line.strip(),
61
                     'match': match})
62
                if substitution is not None:
63
                    line = match_re.sub(substitution, line)
64
            if substitution is not None:
65
                content.append(line)
66
    finally:
67
        file_.close()
68
    if lines:
69
        if substitution is not None:
70
            file_ = open(file_path, 'w')
71
            try:
72
                file_.write(''.join(content))
73
            finally:
74
                file_.close()
75
        return {'file_path' : file_path, 'lines' : lines}
76
    return None
77
78
79
def get_option_parser():
80
    """Return the option parser for this program."""
81
    usage = "usage: %prog [options] root_dir file_pattern match"
82
    parser = OptionParser(usage=usage)
83
    parser.add_option(
84
        "-s", "--substitution", dest="substitution",
85
        help="The substitution string (may contain \\[0-9] match groups).")
86
    parser.set_defaults(substitution=None)
87
    return parser
88
89
90
def main(argv=None):
91
    """Run the command line operations."""
92
    if argv is None:
93
        argv = sys.argv
94
    parser = get_option_parser()
95
    (options, args) = parser.parse_args(args=argv[1:])
96
97
    root_dir = args[0]
98
    file_pattern = args[1]
99
    match_pattern = args[2]
100
    substitution = options.substitution
101
    print "Looking for [%s] in files like %s under %s:" % (
102
        match_pattern, file_pattern, root_dir)
103
    for summary in find_matches(
104
        root_dir, file_pattern, match_pattern, substitution=substitution):
105
        print "\n%(file_path)s" % summary
106
        for line in summary['lines']:
107
            print "    %(lineno)4s: %(text)s" % line
108
109
110
if __name__ == '__main__':
111
    sys.exit(main())