blob: bcbdbf67d924cb30e59b55b1f5f0f009f7ef9fd1 [file] [log] [blame]
satorux3d23ca02015-02-10 08:04:511#!/usr/bin/env python
2# Copyright 2015 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
satoruxfb19c782015-02-12 04:02:046"""Given a GYP/GN filename, sort C-ish source files in that file.
satorux3d23ca02015-02-10 08:04:517
8Shows a diff and prompts for confirmation before doing the deed.
9Works great with tools/git/for-all-touched-files.py.
satorux2f6126992015-02-20 00:37:0110
11Limitations:
12
131) Comments used as section headers
14
15If a comment (1+ lines starting with #) appears in a source list without a
16preceding blank line, the tool assumes that the comment is about the next
17line. For example, given the following source list,
18
19 sources = [
20 "b.cc",
21 # Comment.
22 "a.cc",
23 "c.cc",
24 ]
25
26the tool will produce the following output:
27
28 sources = [
29 # Comment.
30 "a.cc",
31 "b.cc",
32 "c.cc",
33 ]
34
35This is not correct if the comment is for starting a new section like:
36
37 sources = [
38 "b.cc",
39 # These are for Linux.
40 "a.cc",
41 "c.cc",
42 ]
43
44The tool cannot disambiguate the two types of comments. The problem can be
45worked around by inserting a blank line before the comment because the tool
46interprets a blank line as the end of a source list.
47
482) Sources commented out
49
50Sometimes sources are commented out with their positions kept in the
51alphabetical order, but what if the list is not sorted correctly? For
52example, given the following source list,
53
54 sources = [
55 "a.cc",
56 # "b.cc",
57 "d.cc",
58 "c.cc",
59 ]
60
61the tool will produce the following output:
62
63 sources = [
64 "a.cc",
65 "c.cc",
66 # "b.cc",
67 "d.cc",
68 ]
69
70This is because the tool assumes that the comment (# "b.cc",) is about the
71next line ("d.cc",). This kind of errors should be fixed manually, or the
72commented-out code should be deleted.
73
743) " and ' are used both used in the same source list (GYP only problem)
75
76If both " and ' are used in the same source list, sources quoted with " will
77appear first in the output. The problem is rare enough so the tool does not
78attempt to normalize them. Hence this kind of errors should be fixed
79manually.
80
814) Spaces and tabs used in the same source list
82
83Similarly, if spaces and tabs are both used in the same source list, sources
84indented with tabs will appear first in the output. This kind of errors
85should be fixed manually.
86
satorux3d23ca02015-02-10 08:04:5187"""
88
89import difflib
90import optparse
91import re
92import sys
93
94from yes_no import YesNo
95
satorux72c74092015-02-13 01:56:5996SUFFIXES = ['c', 'cc', 'cpp', 'h', 'mm', 'rc', 'rc.version', 'ico', 'def',
97 'release']
satorux2f6126992015-02-20 00:37:0198SOURCE_PATTERN = re.compile(r'^\s+[\'"].*\.(%s)[\'"],$' %
satorux357fcdaa2015-02-13 04:37:0299 '|'.join([re.escape(x) for x in SUFFIXES]))
satorux2f6126992015-02-20 00:37:01100COMMENT_PATTERN = re.compile(r'^\s+#')
101
satorux3d23ca02015-02-10 08:04:51102
103def SortSources(original_lines):
104 """Sort source file names in |original_lines|.
105
106 Args:
107 original_lines: Lines of the original content as a list of strings.
108
109 Returns:
110 Lines of the sorted content as a list of strings.
111
satorux357fcdaa2015-02-13 04:37:02112 The algorithm is fairly naive. The code tries to find a list of C-ish
113 source file names by a simple regex, then sort them. The code does not try
satorux2f6126992015-02-20 00:37:01114 to understand the syntax of the build files. See the file comment above for
115 details.
satorux3d23ca02015-02-10 08:04:51116 """
117
118 output_lines = []
satorux357fcdaa2015-02-13 04:37:02119 comments = []
satorux3d23ca02015-02-10 08:04:51120 sources = []
121 for line in original_lines:
satorux357fcdaa2015-02-13 04:37:02122 if re.search(COMMENT_PATTERN, line):
123 comments.append(line)
124 elif re.search(SOURCE_PATTERN, line):
satorux2f6126992015-02-20 00:37:01125 # Associate the line with the preceding comments.
satorux357fcdaa2015-02-13 04:37:02126 sources.append([line, comments])
127 comments = []
satorux3d23ca02015-02-10 08:04:51128 else:
satoruxeef164ef2015-02-16 01:39:34129 # |sources| should be flushed first, to handle comments at the end of a
130 # source list correctly.
satorux3d23ca02015-02-10 08:04:51131 if sources:
satorux357fcdaa2015-02-13 04:37:02132 for source_line, source_comments in sorted(sources):
133 output_lines.extend(source_comments)
134 output_lines.append(source_line)
satorux3d23ca02015-02-10 08:04:51135 sources = []
satoruxeef164ef2015-02-16 01:39:34136 if comments:
137 output_lines.extend(comments)
138 comments = []
satorux3d23ca02015-02-10 08:04:51139 output_lines.append(line)
140 return output_lines
141
142
143def ProcessFile(filename, should_confirm):
144 """Process the input file and rewrite if needed.
145
146 Args:
147 filename: Path to the input file.
148 should_confirm: If true, diff and confirmation prompt are shown.
149 """
150
151 original_lines = []
152 with open(filename, 'r') as input_file:
153 for line in input_file:
154 original_lines.append(line)
155
156 new_lines = SortSources(original_lines)
157 if original_lines == new_lines:
158 print '%s: no change' % filename
159 return
160
161 if should_confirm:
162 diff = difflib.unified_diff(original_lines, new_lines)
163 sys.stdout.writelines(diff)
164 if not YesNo('Use new file (y/N)'):
165 return
166
167 with open(filename, 'w') as output_file:
168 output_file.writelines(new_lines)
169
170
171def main():
172 parser = optparse.OptionParser(usage='%prog filename1 filename2 ...')
173 parser.add_option('-f', '--force', action='store_false', default=True,
174 dest='should_confirm',
175 help='Turn off confirmation prompt.')
176 opts, filenames = parser.parse_args()
177
178 if len(filenames) < 1:
179 parser.print_help()
180 return 1
181
182 for filename in filenames:
183 ProcessFile(filename, opts.should_confirm)
184
185
186if __name__ == '__main__':
187 sys.exit(main())