1# Copyright 2015 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Entry points for YAPF. 15 16The main APIs that YAPF exposes to drive the reformatting. 17 18 FormatFile(): reformat a file. 19 FormatCode(): reformat a string of code. 20 21These APIs have some common arguments: 22 23 style_config: (string) Either a style name or a path to a file that contains 24 formatting style settings. If None is specified, use the default style 25 as set in style.DEFAULT_STYLE_FACTORY 26 lines: (list of tuples of integers) A list of tuples of lines, [start, end], 27 that we want to format. The lines are 1-based indexed. It can be used by 28 third-party code (e.g., IDEs) when reformatting a snippet of code rather 29 than a whole file. 30 print_diff: (bool) Instead of returning the reformatted source, return a 31 diff that turns the formatted source into reformatter source. 32 verify: (bool) True if reformatted code should be verified for syntax. 33""" 34 35import difflib 36import re 37import sys 38 39from lib2to3.pgen2 import parse 40from lib2to3.pgen2 import tokenize 41 42from yapf.yapflib import blank_line_calculator 43from yapf.yapflib import comment_splicer 44from yapf.yapflib import continuation_splicer 45from yapf.yapflib import errors 46from yapf.yapflib import file_resources 47from yapf.yapflib import identify_container 48from yapf.yapflib import py3compat 49from yapf.yapflib import pytree_unwrapper 50from yapf.yapflib import pytree_utils 51from yapf.yapflib import reformatter 52from yapf.yapflib import split_penalty 53from yapf.yapflib import style 54from yapf.yapflib import subtype_assigner 55 56 57def FormatFile(filename, 58 style_config=None, 59 lines=None, 60 print_diff=False, 61 verify=False, 62 in_place=False, 63 logger=None): 64 """Format a single Python file and return the formatted code. 65 66 Arguments: 67 filename: (unicode) The file to reformat. 68 style_config: (string) Either a style name or a path to a file that contains 69 formatting style settings. If None is specified, use the default style 70 as set in style.DEFAULT_STYLE_FACTORY 71 lines: (list of tuples of integers) A list of tuples of lines, [start, end], 72 that we want to format. The lines are 1-based indexed. It can be used by 73 third-party code (e.g., IDEs) when reformatting a snippet of code rather 74 than a whole file. 75 print_diff: (bool) Instead of returning the reformatted source, return a 76 diff that turns the formatted source into reformatter source. 77 verify: (bool) True if reformatted code should be verified for syntax. 78 in_place: (bool) If True, write the reformatted code back to the file. 79 logger: (io streamer) A stream to output logging. 80 81 Returns: 82 Tuple of (reformatted_code, encoding, changed). reformatted_code is None if 83 the file is successfully written to (having used in_place). reformatted_code 84 is a diff if print_diff is True. 85 86 Raises: 87 IOError: raised if there was an error reading the file. 88 ValueError: raised if in_place and print_diff are both specified. 89 """ 90 _CheckPythonVersion() 91 92 if in_place and print_diff: 93 raise ValueError('Cannot pass both in_place and print_diff.') 94 95 original_source, newline, encoding = ReadFile(filename, logger) 96 reformatted_source, changed = FormatCode( 97 original_source, 98 style_config=style_config, 99 filename=filename, 100 lines=lines, 101 print_diff=print_diff, 102 verify=verify) 103 if reformatted_source.rstrip('\n'): 104 lines = reformatted_source.rstrip('\n').split('\n') 105 reformatted_source = newline.join(iter(lines)) + newline 106 if in_place: 107 if original_source and original_source != reformatted_source: 108 file_resources.WriteReformattedCode(filename, reformatted_source, 109 encoding, in_place) 110 return None, encoding, changed 111 112 return reformatted_source, encoding, changed 113 114 115def FormatTree(tree, style_config=None, lines=None, verify=False): 116 """Format a parsed lib2to3 pytree. 117 118 This provides an alternative entry point to YAPF. 119 120 Arguments: 121 tree: (pytree.Node) The root of the pytree to format. 122 style_config: (string) Either a style name or a path to a file that contains 123 formatting style settings. If None is specified, use the default style 124 as set in style.DEFAULT_STYLE_FACTORY 125 lines: (list of tuples of integers) A list of tuples of lines, [start, end], 126 that we want to format. The lines are 1-based indexed. It can be used by 127 third-party code (e.g., IDEs) when reformatting a snippet of code rather 128 than a whole file. 129 verify: (bool) True if reformatted code should be verified for syntax. 130 131 Returns: 132 The source formatted according to the given formatting style. 133 """ 134 _CheckPythonVersion() 135 style.SetGlobalStyle(style.CreateStyleFromConfig(style_config)) 136 137 # Run passes on the tree, modifying it in place. 138 comment_splicer.SpliceComments(tree) 139 continuation_splicer.SpliceContinuations(tree) 140 subtype_assigner.AssignSubtypes(tree) 141 identify_container.IdentifyContainers(tree) 142 split_penalty.ComputeSplitPenalties(tree) 143 blank_line_calculator.CalculateBlankLines(tree) 144 145 llines = pytree_unwrapper.UnwrapPyTree(tree) 146 for lline in llines: 147 lline.CalculateFormattingInformation() 148 149 lines = _LineRangesToSet(lines) 150 _MarkLinesToFormat(llines, lines) 151 return reformatter.Reformat(_SplitSemicolons(llines), verify, lines) 152 153 154def FormatCode(unformatted_source, 155 filename='<unknown>', 156 style_config=None, 157 lines=None, 158 print_diff=False, 159 verify=False): 160 """Format a string of Python code. 161 162 This provides an alternative entry point to YAPF. 163 164 Arguments: 165 unformatted_source: (unicode) The code to format. 166 filename: (unicode) The name of the file being reformatted. 167 style_config: (string) Either a style name or a path to a file that contains 168 formatting style settings. If None is specified, use the default style 169 as set in style.DEFAULT_STYLE_FACTORY 170 lines: (list of tuples of integers) A list of tuples of lines, [start, end], 171 that we want to format. The lines are 1-based indexed. It can be used by 172 third-party code (e.g., IDEs) when reformatting a snippet of code rather 173 than a whole file. 174 print_diff: (bool) Instead of returning the reformatted source, return a 175 diff that turns the formatted source into reformatter source. 176 verify: (bool) True if reformatted code should be verified for syntax. 177 178 Returns: 179 Tuple of (reformatted_source, changed). reformatted_source conforms to the 180 desired formatting style. changed is True if the source changed. 181 """ 182 try: 183 tree = pytree_utils.ParseCodeToTree(unformatted_source) 184 except Exception as e: 185 e.filename = filename 186 raise errors.YapfError(errors.FormatErrorMsg(e)) 187 188 reformatted_source = FormatTree( 189 tree, style_config=style_config, lines=lines, verify=verify) 190 191 if unformatted_source == reformatted_source: 192 return '' if print_diff else reformatted_source, False 193 194 code_diff = _GetUnifiedDiff( 195 unformatted_source, reformatted_source, filename=filename) 196 197 if print_diff: 198 return code_diff, code_diff.strip() != '' # pylint: disable=g-explicit-bool-comparison # noqa 199 200 return reformatted_source, True 201 202 203def _CheckPythonVersion(): # pragma: no cover 204 errmsg = 'yapf is only supported for Python 2.7 or 3.4+' 205 if sys.version_info[0] == 2: 206 if sys.version_info[1] < 7: 207 raise RuntimeError(errmsg) 208 elif sys.version_info[0] == 3: 209 if sys.version_info[1] < 4: 210 raise RuntimeError(errmsg) 211 212 213def ReadFile(filename, logger=None): 214 """Read the contents of the file. 215 216 An optional logger can be specified to emit messages to your favorite logging 217 stream. If specified, then no exception is raised. This is external so that it 218 can be used by third-party applications. 219 220 Arguments: 221 filename: (unicode) The name of the file. 222 logger: (function) A function or lambda that takes a string and emits it. 223 224 Returns: 225 The contents of filename. 226 227 Raises: 228 IOError: raised if there was an error reading the file. 229 """ 230 try: 231 encoding = file_resources.FileEncoding(filename) 232 233 # Preserves line endings. 234 with py3compat.open_with_encoding( 235 filename, mode='r', encoding=encoding, newline='') as fd: 236 lines = fd.readlines() 237 238 line_ending = file_resources.LineEnding(lines) 239 source = '\n'.join(line.rstrip('\r\n') for line in lines) + '\n' 240 return source, line_ending, encoding 241 except IOError as e: # pragma: no cover 242 if logger: 243 logger(e) 244 e.args = (e.args[0], (filename, e.args[1][1], e.args[1][2], e.args[1][3])) 245 raise 246 except UnicodeDecodeError as e: # pragma: no cover 247 if logger: 248 logger('Could not parse %s! Consider excluding this file with --exclude.', 249 filename) 250 logger(e) 251 e.args = (e.args[0], (filename, e.args[1][1], e.args[1][2], e.args[1][3])) 252 raise 253 254 255def _SplitSemicolons(lines): 256 res = [] 257 for line in lines: 258 res.extend(line.Split()) 259 return res 260 261 262DISABLE_PATTERN = r'^#.*\byapf:\s*disable\b' 263ENABLE_PATTERN = r'^#.*\byapf:\s*enable\b' 264 265 266def _LineRangesToSet(line_ranges): 267 """Return a set of lines in the range.""" 268 269 if line_ranges is None: 270 return None 271 272 line_set = set() 273 for low, high in sorted(line_ranges): 274 line_set.update(range(low, high + 1)) 275 276 return line_set 277 278 279def _MarkLinesToFormat(llines, lines): 280 """Skip sections of code that we shouldn't reformat.""" 281 if lines: 282 for uwline in llines: 283 uwline.disable = not lines.intersection( 284 range(uwline.lineno, uwline.last.lineno + 1)) 285 286 # Now go through the lines and disable any lines explicitly marked as 287 # disabled. 288 index = 0 289 while index < len(llines): 290 uwline = llines[index] 291 if uwline.is_comment: 292 if _DisableYAPF(uwline.first.value.strip()): 293 index += 1 294 while index < len(llines): 295 uwline = llines[index] 296 line = uwline.first.value.strip() 297 if uwline.is_comment and _EnableYAPF(line): 298 if not _DisableYAPF(line): 299 break 300 uwline.disable = True 301 index += 1 302 elif re.search(DISABLE_PATTERN, uwline.last.value.strip(), re.IGNORECASE): 303 uwline.disable = True 304 index += 1 305 306 307def _DisableYAPF(line): 308 return (re.search(DISABLE_PATTERN, 309 line.split('\n')[0].strip(), re.IGNORECASE) or 310 re.search(DISABLE_PATTERN, 311 line.split('\n')[-1].strip(), re.IGNORECASE)) 312 313 314def _EnableYAPF(line): 315 return (re.search(ENABLE_PATTERN, 316 line.split('\n')[0].strip(), re.IGNORECASE) or 317 re.search(ENABLE_PATTERN, 318 line.split('\n')[-1].strip(), re.IGNORECASE)) 319 320 321def _GetUnifiedDiff(before, after, filename='code'): 322 """Get a unified diff of the changes. 323 324 Arguments: 325 before: (unicode) The original source code. 326 after: (unicode) The reformatted source code. 327 filename: (unicode) The code's filename. 328 329 Returns: 330 The unified diff text. 331 """ 332 before = before.splitlines() 333 after = after.splitlines() 334 return '\n'.join( 335 difflib.unified_diff( 336 before, 337 after, 338 filename, 339 filename, 340 '(original)', 341 '(reformatted)', 342 lineterm='')) + '\n' 343