blob: 1a1df013ade3ea12806be22dec99785082ae1ccd [file] [log] [blame]
Benjamin Peterson8951b612008-09-03 02:27:16 +00001"""
2Main program for 2to3.
3"""
4
Zachary Ware2acbae82014-10-29 12:24:59 -05005from __future__ import with_statement, print_function
Benjamin Peterson8d26b0b2010-05-07 19:10:11 +00006
Benjamin Peterson8951b612008-09-03 02:27:16 +00007import sys
8import os
Benjamin Peterson3059b002009-07-20 16:42:03 +00009import difflib
Benjamin Peterson8951b612008-09-03 02:27:16 +000010import logging
Benjamin Peterson0b24b3d2008-12-16 03:57:54 +000011import shutil
Benjamin Peterson8951b612008-09-03 02:27:16 +000012import optparse
13
14from . import refactor
15
Benjamin Peterson3059b002009-07-20 16:42:03 +000016
17def diff_texts(a, b, filename):
18 """Return a unified diff of two strings."""
19 a = a.splitlines()
20 b = b.splitlines()
21 return difflib.unified_diff(a, b, filename, filename,
22 "(original)", "(refactored)",
23 lineterm="")
24
25
Benjamin Peterson608d8bc2009-05-05 23:23:31 +000026class StdoutRefactoringTool(refactor.MultiprocessRefactoringTool):
Benjamin Petersond61de7f2008-09-27 22:17:35 +000027 """
Gregory P. Smith58f23ff2012-02-12 15:50:21 -080028 A refactoring tool that can avoid overwriting its input files.
Benjamin Petersond61de7f2008-09-27 22:17:35 +000029 Prints output to stdout.
Gregory P. Smith58f23ff2012-02-12 15:50:21 -080030
31 Output files can optionally be written to a different directory and or
32 have an extra file suffix appended to their name for use in situations
33 where you do not want to replace the input files.
Benjamin Petersond61de7f2008-09-27 22:17:35 +000034 """
35
Gregory P. Smith58f23ff2012-02-12 15:50:21 -080036 def __init__(self, fixers, options, explicit, nobackups, show_diffs,
37 input_base_dir='', output_dir='', append_suffix=''):
38 """
39 Args:
40 fixers: A list of fixers to import.
41 options: A dict with RefactoringTool configuration.
42 explicit: A list of fixers to run even if they are explicit.
43 nobackups: If true no backup '.bak' files will be created for those
44 files that are being refactored.
45 show_diffs: Should diffs of the refactoring be printed to stdout?
46 input_base_dir: The base directory for all input files. This class
47 will strip this path prefix off of filenames before substituting
48 it with output_dir. Only meaningful if output_dir is supplied.
49 All files processed by refactor() must start with this path.
50 output_dir: If supplied, all converted files will be written into
51 this directory tree instead of input_base_dir.
52 append_suffix: If supplied, all files output by this tool will have
53 this appended to their filename. Useful for changing .py to
54 .py3 for example by passing append_suffix='3'.
55 """
Benjamin Peterson206e3072008-10-19 14:07:49 +000056 self.nobackups = nobackups
Benjamin Peterson3059b002009-07-20 16:42:03 +000057 self.show_diffs = show_diffs
Gregory P. Smith58f23ff2012-02-12 15:50:21 -080058 if input_base_dir and not input_base_dir.endswith(os.sep):
59 input_base_dir += os.sep
60 self._input_base_dir = input_base_dir
61 self._output_dir = output_dir
62 self._append_suffix = append_suffix
Benjamin Peterson206e3072008-10-19 14:07:49 +000063 super(StdoutRefactoringTool, self).__init__(fixers, options, explicit)
64
Benjamin Petersond61de7f2008-09-27 22:17:35 +000065 def log_error(self, msg, *args, **kwargs):
66 self.errors.append((msg, args, kwargs))
67 self.logger.error(msg, *args, **kwargs)
68
Benjamin Petersond481e3d2009-05-09 19:42:23 +000069 def write_file(self, new_text, filename, old_text, encoding):
Gregory P. Smith58f23ff2012-02-12 15:50:21 -080070 orig_filename = filename
71 if self._output_dir:
72 if filename.startswith(self._input_base_dir):
73 filename = os.path.join(self._output_dir,
74 filename[len(self._input_base_dir):])
75 else:
76 raise ValueError('filename %s does not start with the '
77 'input_base_dir %s' % (
78 filename, self._input_base_dir))
79 if self._append_suffix:
80 filename += self._append_suffix
81 if orig_filename != filename:
Gregory P. Smithefc66f92012-02-12 15:58:36 -080082 output_dir = os.path.dirname(filename)
83 if not os.path.isdir(output_dir):
84 os.makedirs(output_dir)
85 self.log_message('Writing converted %s to %s.', orig_filename,
86 filename)
Benjamin Peterson206e3072008-10-19 14:07:49 +000087 if not self.nobackups:
88 # Make backup
89 backup = filename + ".bak"
90 if os.path.lexists(backup):
91 try:
92 os.remove(backup)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +020093 except OSError as err:
Benjamin Peterson206e3072008-10-19 14:07:49 +000094 self.log_message("Can't remove backup %s", backup)
95 try:
96 os.rename(filename, backup)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +020097 except OSError as err:
Benjamin Peterson206e3072008-10-19 14:07:49 +000098 self.log_message("Can't rename %s to %s", filename, backup)
99 # Actually write the new file
Benjamin Petersond481e3d2009-05-09 19:42:23 +0000100 write = super(StdoutRefactoringTool, self).write_file
101 write(new_text, filename, old_text, encoding)
Benjamin Peterson92035012008-12-27 16:00:54 +0000102 if not self.nobackups:
Benjamin Peterson8bcddca2009-01-03 16:53:14 +0000103 shutil.copymode(backup, filename)
Gregory P. Smith58f23ff2012-02-12 15:50:21 -0800104 if orig_filename != filename:
105 # Preserve the file mode in the new output directory.
106 shutil.copymode(orig_filename, filename)
Benjamin Peterson206e3072008-10-19 14:07:49 +0000107
Benjamin Peterson3059b002009-07-20 16:42:03 +0000108 def print_output(self, old, new, filename, equal):
109 if equal:
110 self.log_message("No changes to %s", filename)
111 else:
112 self.log_message("Refactored %s", filename)
113 if self.show_diffs:
Benjamin Petersonffa94b02009-12-29 00:06:20 +0000114 diff_lines = diff_texts(old, new, filename)
115 try:
Benjamin Peterson8d26b0b2010-05-07 19:10:11 +0000116 if self.output_lock is not None:
117 with self.output_lock:
118 for line in diff_lines:
119 print(line)
120 sys.stdout.flush()
121 else:
122 for line in diff_lines:
123 print(line)
Benjamin Petersonffa94b02009-12-29 00:06:20 +0000124 except UnicodeEncodeError:
125 warn("couldn't encode %s's diff for your terminal" %
126 (filename,))
127 return
Benjamin Peterson3059b002009-07-20 16:42:03 +0000128
129def warn(msg):
Benjamin Peterson8fb00be2009-09-25 20:34:04 +0000130 print("WARNING: %s" % (msg,), file=sys.stderr)
Benjamin Petersond61de7f2008-09-27 22:17:35 +0000131
132
Benjamin Peterson8951b612008-09-03 02:27:16 +0000133def main(fixer_pkg, args=None):
134 """Main program.
135
136 Args:
137 fixer_pkg: the name of a package where the fixers are located.
138 args: optional; a list of command line arguments. If omitted,
139 sys.argv[1:] is used.
140
141 Returns a suggested exit status (0, 1, 2).
142 """
143 # Set up option parser
Benjamin Peterson0b24b3d2008-12-16 03:57:54 +0000144 parser = optparse.OptionParser(usage="2to3 [options] file|dir ...")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000145 parser.add_option("-d", "--doctests_only", action="store_true",
146 help="Fix up doctests only")
147 parser.add_option("-f", "--fix", action="append", default=[],
Benjamin Peterson206e3072008-10-19 14:07:49 +0000148 help="Each FIX specifies a transformation; default: all")
Benjamin Peterson608d8bc2009-05-05 23:23:31 +0000149 parser.add_option("-j", "--processes", action="store", default=1,
150 type="int", help="Run 2to3 concurrently")
Benjamin Peterson206e3072008-10-19 14:07:49 +0000151 parser.add_option("-x", "--nofix", action="append", default=[],
Martin v. Löwise2bb4eb2010-12-03 23:11:07 +0000152 help="Prevent a transformation from being run")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000153 parser.add_option("-l", "--list-fixes", action="store_true",
Benjamin Peterson8d26b0b2010-05-07 19:10:11 +0000154 help="List available transformations")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000155 parser.add_option("-p", "--print-function", action="store_true",
Benjamin Peterson20211002009-11-25 18:34:42 +0000156 help="Modify the grammar so that print() is a function")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000157 parser.add_option("-v", "--verbose", action="store_true",
158 help="More verbose logging")
Benjamin Peterson3059b002009-07-20 16:42:03 +0000159 parser.add_option("--no-diffs", action="store_true",
160 help="Don't show diffs of the refactoring")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000161 parser.add_option("-w", "--write", action="store_true",
162 help="Write back modified files")
Benjamin Peterson206e3072008-10-19 14:07:49 +0000163 parser.add_option("-n", "--nobackups", action="store_true", default=False,
Martin v. Löwise2bb4eb2010-12-03 23:11:07 +0000164 help="Don't write backups for modified files")
Gregory P. Smith58f23ff2012-02-12 15:50:21 -0800165 parser.add_option("-o", "--output-dir", action="store", type="str",
166 default="", help="Put output files in this directory "
167 "instead of overwriting the input files. Requires -n.")
168 parser.add_option("-W", "--write-unchanged-files", action="store_true",
169 help="Also write files even if no changes were required"
170 " (useful with --output-dir); implies -w.")
171 parser.add_option("--add-suffix", action="store", type="str", default="",
172 help="Append this string to all output filenames."
173 " Requires -n if non-empty. "
174 "ex: --add-suffix='3' will generate .py3 files.")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000175
176 # Parse command line arguments
177 refactor_stdin = False
Benjamin Peterson20211002009-11-25 18:34:42 +0000178 flags = {}
Benjamin Peterson8951b612008-09-03 02:27:16 +0000179 options, args = parser.parse_args(args)
Gregory P. Smith58f23ff2012-02-12 15:50:21 -0800180 if options.write_unchanged_files:
181 flags["write_unchanged_files"] = True
182 if not options.write:
183 warn("--write-unchanged-files/-W implies -w.")
184 options.write = True
185 # If we allowed these, the original files would be renamed to backup names
186 # but not replaced.
187 if options.output_dir and not options.nobackups:
188 parser.error("Can't use --output-dir/-o without -n.")
189 if options.add_suffix and not options.nobackups:
190 parser.error("Can't use --add-suffix without -n.")
191
Benjamin Peterson3059b002009-07-20 16:42:03 +0000192 if not options.write and options.no_diffs:
193 warn("not writing files and not printing diffs; that's not very useful")
Benjamin Peterson206e3072008-10-19 14:07:49 +0000194 if not options.write and options.nobackups:
195 parser.error("Can't use -n without -w")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000196 if options.list_fixes:
Benjamin Petersonc0a40ab2008-09-11 21:05:25 +0000197 print("Available transformations for the -f/--fix option:")
Benjamin Peterson8951b612008-09-03 02:27:16 +0000198 for fixname in refactor.get_all_fix_names(fixer_pkg):
Benjamin Petersonc0a40ab2008-09-11 21:05:25 +0000199 print(fixname)
Benjamin Peterson8951b612008-09-03 02:27:16 +0000200 if not args:
201 return 0
202 if not args:
Benjamin Petersonc0a40ab2008-09-11 21:05:25 +0000203 print("At least one file or directory argument required.", file=sys.stderr)
204 print("Use --help to show usage.", file=sys.stderr)
Benjamin Peterson8951b612008-09-03 02:27:16 +0000205 return 2
206 if "-" in args:
207 refactor_stdin = True
208 if options.write:
Benjamin Petersonc0a40ab2008-09-11 21:05:25 +0000209 print("Can't write to stdin.", file=sys.stderr)
Benjamin Peterson8951b612008-09-03 02:27:16 +0000210 return 2
Benjamin Peterson20211002009-11-25 18:34:42 +0000211 if options.print_function:
212 flags["print_function"] = True
Benjamin Peterson8951b612008-09-03 02:27:16 +0000213
214 # Set up logging handler
215 level = logging.DEBUG if options.verbose else logging.INFO
216 logging.basicConfig(format='%(name)s: %(message)s', level=level)
Gregory P. Smith58f23ff2012-02-12 15:50:21 -0800217 logger = logging.getLogger('lib2to3.main')
Benjamin Peterson8951b612008-09-03 02:27:16 +0000218
219 # Initialize the refactoring tool
Benjamin Peterson206e3072008-10-19 14:07:49 +0000220 avail_fixes = set(refactor.get_fixers_from_package(fixer_pkg))
221 unwanted_fixes = set(fixer_pkg + ".fix_" + fix for fix in options.nofix)
222 explicit = set()
Benjamin Peterson8951b612008-09-03 02:27:16 +0000223 if options.fix:
Benjamin Peterson206e3072008-10-19 14:07:49 +0000224 all_present = False
225 for fix in options.fix:
226 if fix == "all":
227 all_present = True
228 else:
229 explicit.add(fixer_pkg + ".fix_" + fix)
230 requested = avail_fixes.union(explicit) if all_present else explicit
Benjamin Peterson8951b612008-09-03 02:27:16 +0000231 else:
Benjamin Peterson206e3072008-10-19 14:07:49 +0000232 requested = avail_fixes.union(explicit)
233 fixer_names = requested.difference(unwanted_fixes)
Gregory P. Smith58f23ff2012-02-12 15:50:21 -0800234 input_base_dir = os.path.commonprefix(args)
235 if (input_base_dir and not input_base_dir.endswith(os.sep)
236 and not os.path.isdir(input_base_dir)):
237 # One or more similar names were passed, their directory is the base.
238 # os.path.commonprefix() is ignorant of path elements, this corrects
239 # for that weird API.
240 input_base_dir = os.path.dirname(input_base_dir)
241 if options.output_dir:
242 input_base_dir = input_base_dir.rstrip(os.sep)
243 logger.info('Output in %r will mirror the input directory %r layout.',
244 options.output_dir, input_base_dir)
245 rt = StdoutRefactoringTool(
246 sorted(fixer_names), flags, sorted(explicit),
247 options.nobackups, not options.no_diffs,
248 input_base_dir=input_base_dir,
249 output_dir=options.output_dir,
250 append_suffix=options.add_suffix)
Benjamin Peterson8951b612008-09-03 02:27:16 +0000251
252 # Refactor all files and directories passed as arguments
253 if not rt.errors:
254 if refactor_stdin:
255 rt.refactor_stdin()
256 else:
Benjamin Peterson608d8bc2009-05-05 23:23:31 +0000257 try:
258 rt.refactor(args, options.write, options.doctests_only,
259 options.processes)
260 except refactor.MultiprocessingUnsupported:
261 assert options.processes > 1
Benjamin Peterson8fb00be2009-09-25 20:34:04 +0000262 print("Sorry, -j isn't supported on this platform.",
263 file=sys.stderr)
Benjamin Peterson608d8bc2009-05-05 23:23:31 +0000264 return 1
Benjamin Peterson8951b612008-09-03 02:27:16 +0000265 rt.summarize()
266
267 # Return error status (0 if rt.errors is zero)
268 return int(bool(rt.errors))