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.
6 """Given a GYP/GN filename, sort C-ish source files in that file.
8 Shows a diff and prompts for confirmation before doing the deed.
9 Works great with tools/git/for-all-touched-files.py.
13 1) Comments used as section headers
15 If a comment (1+ lines starting with #) appears in a source list without a
16 preceding blank line, the tool assumes that the comment is about the next
17 line. For example, given the following source list,
26 the tool will produce the following output:
35 This is not correct if the comment is for starting a new section like:
39 # These are for Linux.
44 The tool cannot disambiguate the two types of comments. The problem can be
45 worked around by inserting a blank line before the comment because the tool
46 interprets a blank line as the end of a source list.
48 2) Sources commented out
50 Sometimes sources are commented out with their positions kept in the
51 alphabetical order, but what if the list is not sorted correctly? For
52 example, given the following source list,
61 the tool will produce the following output:
70 This is because the tool assumes that the comment (# "b.cc",) is about the
71 next line ("d.cc",). This kind of errors should be fixed manually, or the
72 commented-out code should be deleted.
74 3) " and ' are used both used in the same source list (GYP only problem)
76 If both " and ' are used in the same source list, sources quoted with " will
77 appear first in the output. The problem is rare enough so the tool does not
78 attempt to normalize them. Hence this kind of errors should be fixed
81 4) Spaces and tabs used in the same source list
83 Similarly, if spaces and tabs are both used in the same source list, sources
84 indented with tabs will appear first in the output. This kind of errors
85 should be fixed manually.
94 from yes_no
import YesNo
96 SUFFIXES
= ['c', 'cc', 'cpp', 'h', 'mm', 'rc', 'rc.version', 'ico', 'def',
98 SOURCE_PATTERN
= re
.compile(r
'^\s+[\'"].*\.(%s)[\'"],$
' %
99 '|
'.join([re.escape(x) for x in SUFFIXES]))
100 COMMENT_PATTERN = re.compile(r'^\s
+#')
103 def SortSources(original_lines
):
104 """Sort source file names in |original_lines|.
107 original_lines: Lines of the original content as a list of strings.
110 Lines of the sorted content as a list of strings.
112 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
114 to understand the syntax of the build files. See the file comment above for
121 for line
in original_lines
:
122 if re
.search(COMMENT_PATTERN
, line
):
123 comments
.append(line
)
124 elif re
.search(SOURCE_PATTERN
, line
):
125 # Associate the line with the preceding comments.
126 sources
.append([line
, comments
])
129 # |sources| should be flushed first, to handle comments at the end of a
130 # source list correctly.
132 for source_line
, source_comments
in sorted(sources
):
133 output_lines
.extend(source_comments
)
134 output_lines
.append(source_line
)
137 output_lines
.extend(comments
)
139 output_lines
.append(line
)
143 def ProcessFile(filename
, should_confirm
):
144 """Process the input file and rewrite if needed.
147 filename: Path to the input file.
148 should_confirm: If true, diff and confirmation prompt are shown.
152 with
open(filename
, 'r') as input_file
:
153 for line
in input_file
:
154 original_lines
.append(line
)
156 new_lines
= SortSources(original_lines
)
157 if original_lines
== new_lines
:
158 print '%s: no change' % filename
162 diff
= difflib
.unified_diff(original_lines
, new_lines
)
163 sys
.stdout
.writelines(diff
)
164 if not YesNo('Use new file (y/N)'):
167 with
open(filename
, 'w') as output_file
:
168 output_file
.writelines(new_lines
)
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()
178 if len(filenames
) < 1:
182 for filename
in filenames
:
183 ProcessFile(filename
, opts
.should_confirm
)
186 if __name__
== '__main__':