comparison env/lib/python3.7/site-packages/libfuturize/main.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 """
2 futurize: automatic conversion to clean 2/3 code using ``python-future``
3 ======================================================================
4
5 Like Armin Ronacher's modernize.py, ``futurize`` attempts to produce clean
6 standard Python 3 code that runs on both Py2 and Py3.
7
8 One pass
9 --------
10
11 Use it like this on Python 2 code:
12
13 $ futurize --verbose mypython2script.py
14
15 This will attempt to port the code to standard Py3 code that also
16 provides Py2 compatibility with the help of the right imports from
17 ``future``.
18
19 To write changes to the files, use the -w flag.
20
21 Two stages
22 ----------
23
24 The ``futurize`` script can also be called in two separate stages. First:
25
26 $ futurize --stage1 mypython2script.py
27
28 This produces more modern Python 2 code that is not yet compatible with Python
29 3. The tests should still run and the diff should be uncontroversial to apply to
30 most Python projects that are willing to drop support for Python 2.5 and lower.
31
32 After this, the recommended approach is to explicitly mark all strings that must
33 be byte-strings with a b'' prefix and all text (unicode) strings with a u''
34 prefix, and then invoke the second stage of Python 2 to 2/3 conversion with::
35
36 $ futurize --stage2 mypython2script.py
37
38 Stage 2 adds a dependency on ``future``. It converts most remaining Python
39 2-specific code to Python 3 code and adds appropriate imports from ``future``
40 to restore Py2 support.
41
42 The command above leaves all unadorned string literals as native strings
43 (byte-strings on Py2, unicode strings on Py3). If instead you would like all
44 unadorned string literals to be promoted to unicode, you can also pass this
45 flag:
46
47 $ futurize --stage2 --unicode-literals mypython2script.py
48
49 This adds the declaration ``from __future__ import unicode_literals`` to the
50 top of each file, which implicitly declares all unadorned string literals to be
51 unicode strings (``unicode`` on Py2).
52
53 All imports
54 -----------
55
56 The --all-imports option forces adding all ``__future__`` imports,
57 ``builtins`` imports, and standard library aliases, even if they don't
58 seem necessary for the current state of each module. (This can simplify
59 testing, and can reduce the need to think about Py2 compatibility when editing
60 the code further.)
61
62 """
63
64 from __future__ import (absolute_import, print_function, unicode_literals)
65 import future.utils
66 from future import __version__
67
68 import sys
69 import logging
70 import optparse
71 import os
72
73 from lib2to3.main import warn, StdoutRefactoringTool
74 from lib2to3 import refactor
75
76 from libfuturize.fixes import (lib2to3_fix_names_stage1,
77 lib2to3_fix_names_stage2,
78 libfuturize_fix_names_stage1,
79 libfuturize_fix_names_stage2)
80
81 fixer_pkg = 'libfuturize.fixes'
82
83
84 def main(args=None):
85 """Main program.
86
87 Args:
88 fixer_pkg: the name of a package where the fixers are located.
89 args: optional; a list of command line arguments. If omitted,
90 sys.argv[1:] is used.
91
92 Returns a suggested exit status (0, 1, 2).
93 """
94
95 # Set up option parser
96 parser = optparse.OptionParser(usage="futurize [options] file|dir ...")
97 parser.add_option("-V", "--version", action="store_true",
98 help="Report the version number of futurize")
99 parser.add_option("-a", "--all-imports", action="store_true",
100 help="Add all __future__ and future imports to each module")
101 parser.add_option("-1", "--stage1", action="store_true",
102 help="Modernize Python 2 code only; no compatibility with Python 3 (or dependency on ``future``)")
103 parser.add_option("-2", "--stage2", action="store_true",
104 help="Take modernized (stage1) code and add a dependency on ``future`` to provide Py3 compatibility.")
105 parser.add_option("-0", "--both-stages", action="store_true",
106 help="Apply both stages 1 and 2")
107 parser.add_option("-u", "--unicode-literals", action="store_true",
108 help="Add ``from __future__ import unicode_literals`` to implicitly convert all unadorned string literals '' into unicode strings")
109 parser.add_option("-f", "--fix", action="append", default=[],
110 help="Each FIX specifies a transformation; default: all.\nEither use '-f division -f metaclass' etc. or use the fully-qualified module name: '-f lib2to3.fixes.fix_types -f libfuturize.fixes.fix_unicode_keep_u'")
111 parser.add_option("-j", "--processes", action="store", default=1,
112 type="int", help="Run 2to3 concurrently")
113 parser.add_option("-x", "--nofix", action="append", default=[],
114 help="Prevent a fixer from being run.")
115 parser.add_option("-l", "--list-fixes", action="store_true",
116 help="List available transformations")
117 parser.add_option("-p", "--print-function", action="store_true",
118 help="Modify the grammar so that print() is a function")
119 parser.add_option("-v", "--verbose", action="store_true",
120 help="More verbose logging")
121 parser.add_option("--no-diffs", action="store_true",
122 help="Don't show diffs of the refactoring")
123 parser.add_option("-w", "--write", action="store_true",
124 help="Write back modified files")
125 parser.add_option("-n", "--nobackups", action="store_true", default=False,
126 help="Don't write backups for modified files.")
127 parser.add_option("-o", "--output-dir", action="store", type="str",
128 default="", help="Put output files in this directory "
129 "instead of overwriting the input files. Requires -n. "
130 "For Python >= 2.7 only.")
131 parser.add_option("-W", "--write-unchanged-files", action="store_true",
132 help="Also write files even if no changes were required"
133 " (useful with --output-dir); implies -w.")
134 parser.add_option("--add-suffix", action="store", type="str", default="",
135 help="Append this string to all output filenames."
136 " Requires -n if non-empty. For Python >= 2.7 only."
137 "ex: --add-suffix='3' will generate .py3 files.")
138
139 # Parse command line arguments
140 flags = {}
141 refactor_stdin = False
142 options, args = parser.parse_args(args)
143
144 if options.write_unchanged_files:
145 flags["write_unchanged_files"] = True
146 if not options.write:
147 warn("--write-unchanged-files/-W implies -w.")
148 options.write = True
149 # If we allowed these, the original files would be renamed to backup names
150 # but not replaced.
151 if options.output_dir and not options.nobackups:
152 parser.error("Can't use --output-dir/-o without -n.")
153 if options.add_suffix and not options.nobackups:
154 parser.error("Can't use --add-suffix without -n.")
155
156 if not options.write and options.no_diffs:
157 warn("not writing files and not printing diffs; that's not very useful")
158 if not options.write and options.nobackups:
159 parser.error("Can't use -n without -w")
160 if "-" in args:
161 refactor_stdin = True
162 if options.write:
163 print("Can't write to stdin.", file=sys.stderr)
164 return 2
165 # Is this ever necessary?
166 if options.print_function:
167 flags["print_function"] = True
168
169 # Set up logging handler
170 level = logging.DEBUG if options.verbose else logging.INFO
171 logging.basicConfig(format='%(name)s: %(message)s', level=level)
172 logger = logging.getLogger('libfuturize.main')
173
174 if options.stage1 or options.stage2:
175 assert options.both_stages is None
176 options.both_stages = False
177 else:
178 options.both_stages = True
179
180 avail_fixes = set()
181
182 if options.stage1 or options.both_stages:
183 avail_fixes.update(lib2to3_fix_names_stage1)
184 avail_fixes.update(libfuturize_fix_names_stage1)
185 if options.stage2 or options.both_stages:
186 avail_fixes.update(lib2to3_fix_names_stage2)
187 avail_fixes.update(libfuturize_fix_names_stage2)
188
189 if options.unicode_literals:
190 avail_fixes.add('libfuturize.fixes.fix_unicode_literals_import')
191
192 if options.version:
193 print(__version__)
194 return 0
195 if options.list_fixes:
196 print("Available transformations for the -f/--fix option:")
197 # for fixname in sorted(refactor.get_all_fix_names(fixer_pkg)):
198 for fixname in sorted(avail_fixes):
199 print(fixname)
200 if not args:
201 return 0
202 if not args:
203 print("At least one file or directory argument required.",
204 file=sys.stderr)
205 print("Use --help to show usage.", file=sys.stderr)
206 return 2
207
208 unwanted_fixes = set()
209 for fix in options.nofix:
210 if ".fix_" in fix:
211 unwanted_fixes.add(fix)
212 else:
213 # Infer the full module name for the fixer.
214 # First ensure that no names clash (e.g.
215 # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
216 found = [f for f in avail_fixes
217 if f.endswith('fix_{0}'.format(fix))]
218 if len(found) > 1:
219 print("Ambiguous fixer name. Choose a fully qualified "
220 "module name instead from these:\n" +
221 "\n".join(" " + myf for myf in found),
222 file=sys.stderr)
223 return 2
224 elif len(found) == 0:
225 print("Unknown fixer. Use --list-fixes or -l for a list.",
226 file=sys.stderr)
227 return 2
228 unwanted_fixes.add(found[0])
229
230 extra_fixes = set()
231 if options.all_imports:
232 if options.stage1:
233 prefix = 'libfuturize.fixes.'
234 extra_fixes.add(prefix +
235 'fix_add__future__imports_except_unicode_literals')
236 else:
237 # In case the user hasn't run stage1 for some reason:
238 prefix = 'libpasteurize.fixes.'
239 extra_fixes.add(prefix + 'fix_add_all__future__imports')
240 extra_fixes.add(prefix + 'fix_add_future_standard_library_import')
241 extra_fixes.add(prefix + 'fix_add_all_future_builtins')
242 explicit = set()
243 if options.fix:
244 all_present = False
245 for fix in options.fix:
246 if fix == 'all':
247 all_present = True
248 else:
249 if ".fix_" in fix:
250 explicit.add(fix)
251 else:
252 # Infer the full module name for the fixer.
253 # First ensure that no names clash (e.g.
254 # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
255 found = [f for f in avail_fixes
256 if f.endswith('fix_{0}'.format(fix))]
257 if len(found) > 1:
258 print("Ambiguous fixer name. Choose a fully qualified "
259 "module name instead from these:\n" +
260 "\n".join(" " + myf for myf in found),
261 file=sys.stderr)
262 return 2
263 elif len(found) == 0:
264 print("Unknown fixer. Use --list-fixes or -l for a list.",
265 file=sys.stderr)
266 return 2
267 explicit.add(found[0])
268 if len(explicit & unwanted_fixes) > 0:
269 print("Conflicting usage: the following fixers have been "
270 "simultaneously requested and disallowed:\n" +
271 "\n".join(" " + myf for myf in (explicit & unwanted_fixes)),
272 file=sys.stderr)
273 return 2
274 requested = avail_fixes.union(explicit) if all_present else explicit
275 else:
276 requested = avail_fixes.union(explicit)
277 fixer_names = (requested | extra_fixes) - unwanted_fixes
278
279 input_base_dir = os.path.commonprefix(args)
280 if (input_base_dir and not input_base_dir.endswith(os.sep)
281 and not os.path.isdir(input_base_dir)):
282 # One or more similar names were passed, their directory is the base.
283 # os.path.commonprefix() is ignorant of path elements, this corrects
284 # for that weird API.
285 input_base_dir = os.path.dirname(input_base_dir)
286 if options.output_dir:
287 input_base_dir = input_base_dir.rstrip(os.sep)
288 logger.info('Output in %r will mirror the input directory %r layout.',
289 options.output_dir, input_base_dir)
290
291 # Initialize the refactoring tool
292 if future.utils.PY26:
293 extra_kwargs = {}
294 else:
295 extra_kwargs = {
296 'append_suffix': options.add_suffix,
297 'output_dir': options.output_dir,
298 'input_base_dir': input_base_dir,
299 }
300
301 rt = StdoutRefactoringTool(
302 sorted(fixer_names), flags, sorted(explicit),
303 options.nobackups, not options.no_diffs,
304 **extra_kwargs)
305
306 # Refactor all files and directories passed as arguments
307 if not rt.errors:
308 if refactor_stdin:
309 rt.refactor_stdin()
310 else:
311 try:
312 rt.refactor(args, options.write, None,
313 options.processes)
314 except refactor.MultiprocessingUnsupported:
315 assert options.processes > 1
316 print("Sorry, -j isn't " \
317 "supported on this platform.", file=sys.stderr)
318 return 1
319 rt.summarize()
320
321 # Return error status (0 if rt.errors is zero)
322 return int(bool(rt.errors))