1# Copyright 2015-2017 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"""Python formatting style settings.""" 15 16import os 17import re 18import textwrap 19 20from yapf.yapflib import errors 21from yapf.yapflib import py3compat 22 23 24class StyleConfigError(errors.YapfError): 25 """Raised when there's a problem reading the style configuration.""" 26 pass 27 28 29def Get(setting_name): 30 """Get a style setting.""" 31 return _style[setting_name] 32 33 34def Help(): 35 """Return dict mapping style names to help strings.""" 36 return _STYLE_HELP 37 38 39def SetGlobalStyle(style): 40 """Set a style dict.""" 41 global _style 42 global _GLOBAL_STYLE_FACTORY 43 factory = _GetStyleFactory(style) 44 if factory: 45 _GLOBAL_STYLE_FACTORY = factory 46 _style = style 47 48 49_STYLE_HELP = dict( 50 ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT=textwrap.dedent("""\ 51 Align closing bracket with visual indentation."""), 52 ALLOW_MULTILINE_LAMBDAS=textwrap.dedent("""\ 53 Allow lambdas to be formatted on more than one line."""), 54 ALLOW_MULTILINE_DICTIONARY_KEYS=textwrap.dedent("""\ 55 Allow dictionary keys to exist on multiple lines. For example: 56 57 x = { 58 ('this is the first element of a tuple', 59 'this is the second element of a tuple'): 60 value, 61 }"""), 62 BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF=textwrap.dedent("""\ 63 Insert a blank line before a 'def' or 'class' immediately nested 64 within another 'def' or 'class'. For example: 65 66 class Foo: 67 # <------ this blank line 68 def method(): 69 ..."""), 70 BLANK_LINE_BEFORE_CLASS_DOCSTRING=textwrap.dedent("""\ 71 Insert a blank line before a class-level docstring."""), 72 COALESCE_BRACKETS=textwrap.dedent("""\ 73 Do not split consecutive brackets. Only relevant when 74 dedent_closing_brackets is set. For example: 75 76 call_func_that_takes_a_dict( 77 { 78 'key1': 'value1', 79 'key2': 'value2', 80 } 81 ) 82 83 would reformat to: 84 85 call_func_that_takes_a_dict({ 86 'key1': 'value1', 87 'key2': 'value2', 88 })"""), 89 COLUMN_LIMIT=textwrap.dedent("""\ 90 The column limit."""), 91 CONTINUATION_INDENT_WIDTH=textwrap.dedent("""\ 92 Indent width used for line continuations."""), 93 DEDENT_CLOSING_BRACKETS=textwrap.dedent("""\ 94 Put closing brackets on a separate line, dedented, if the bracketed 95 expression can't fit in a single line. Applies to all kinds of brackets, 96 including function definitions and calls. For example: 97 98 config = { 99 'key1': 'value1', 100 'key2': 'value2', 101 } # <--- this bracket is dedented and on a separate line 102 103 time_series = self.remote_client.query_entity_counters( 104 entity='dev3246.region1', 105 key='dns.query_latency_tcp', 106 transform=Transformation.AVERAGE(window=timedelta(seconds=60)), 107 start_ts=now()-timedelta(days=3), 108 end_ts=now(), 109 ) # <--- this bracket is dedented and on a separate line"""), 110 EACH_DICT_ENTRY_ON_SEPARATE_LINE=textwrap.dedent("""\ 111 Place each dictionary entry onto its own line."""), 112 I18N_COMMENT=textwrap.dedent("""\ 113 The regex for an i18n comment. The presence of this comment stops 114 reformatting of that line, because the comments are required to be 115 next to the string they translate."""), 116 I18N_FUNCTION_CALL=textwrap.dedent("""\ 117 The i18n function call names. The presence of this function stops 118 reformattting on that line, because the string it has cannot be moved 119 away from the i18n comment."""), 120 INDENT_DICTIONARY_VALUE=textwrap.dedent("""\ 121 Indent the dictionary value if it cannot fit on the same line as the 122 dictionary key. For example: 123 124 config = { 125 'key1': 126 'value1', 127 'key2': value1 + 128 value2, 129 }"""), 130 INDENT_WIDTH=textwrap.dedent("""\ 131 The number of columns to use for indentation."""), 132 JOIN_MULTIPLE_LINES=textwrap.dedent("""\ 133 Join short lines into one line. E.g., single line 'if' statements."""), 134 SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=textwrap.dedent("""\ 135 Insert a space between the ending comma and closing bracket of a list, 136 etc."""), 137 SPACES_AROUND_POWER_OPERATOR=textwrap.dedent("""\ 138 Use spaces around the power operator."""), 139 SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=textwrap.dedent("""\ 140 Use spaces around default or named assigns."""), 141 SPACES_BEFORE_COMMENT=textwrap.dedent("""\ 142 The number of spaces required before a trailing comment."""), 143 SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED=textwrap.dedent("""\ 144 Split before arguments if the argument list is terminated by a 145 comma."""), 146 SPLIT_BEFORE_BITWISE_OPERATOR=textwrap.dedent("""\ 147 Set to True to prefer splitting before '&', '|' or '^' rather than 148 after."""), 149 SPLIT_BEFORE_DICT_SET_GENERATOR=textwrap.dedent("""\ 150 Split before a dictionary or set generator (comp_for). For example, note 151 the split before the 'for': 152 153 foo = { 154 variable: 'Hello world, have a nice day!' 155 for variable in bar if variable != 42 156 }"""), 157 SPLIT_BEFORE_FIRST_ARGUMENT=textwrap.dedent("""\ 158 If an argument / parameter list is going to be split, then split before 159 the first argument."""), 160 SPLIT_BEFORE_LOGICAL_OPERATOR=textwrap.dedent("""\ 161 Set to True to prefer splitting before 'and' or 'or' rather than 162 after."""), 163 SPLIT_BEFORE_NAMED_ASSIGNS=textwrap.dedent("""\ 164 Split named assignments onto individual lines."""), 165 SPLIT_PENALTY_AFTER_OPENING_BRACKET=textwrap.dedent("""\ 166 The penalty for splitting right after the opening bracket."""), 167 SPLIT_PENALTY_AFTER_UNARY_OPERATOR=textwrap.dedent("""\ 168 The penalty for splitting the line after a unary operator."""), 169 SPLIT_PENALTY_BEFORE_IF_EXPR=textwrap.dedent("""\ 170 The penalty for splitting right before an if expression."""), 171 SPLIT_PENALTY_BITWISE_OPERATOR=textwrap.dedent("""\ 172 The penalty of splitting the line around the '&', '|', and '^' 173 operators."""), 174 SPLIT_PENALTY_EXCESS_CHARACTER=textwrap.dedent("""\ 175 The penalty for characters over the column limit."""), 176 SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT=textwrap.dedent("""\ 177 The penalty incurred by adding a line split to the unwrapped line. The 178 more line splits added the higher the penalty."""), 179 SPLIT_PENALTY_IMPORT_NAMES=textwrap.dedent("""\ 180 The penalty of splitting a list of "import as" names. For example: 181 182 from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, 183 long_argument_2, 184 long_argument_3) 185 186 would reformat to something like: 187 188 from a_very_long_or_indented_module_name_yada_yad import ( 189 long_argument_1, long_argument_2, long_argument_3) 190 """), 191 SPLIT_PENALTY_LOGICAL_OPERATOR=textwrap.dedent("""\ 192 The penalty of splitting the line around the 'and' and 'or' 193 operators."""), 194 USE_TABS=textwrap.dedent("""\ 195 Use the Tab character for indentation."""), 196 # BASED_ON_STYLE='Which predefined style this style is based on', 197) 198 199 200def CreatePEP8Style(): 201 return dict( 202 ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT=True, 203 ALLOW_MULTILINE_LAMBDAS=False, 204 ALLOW_MULTILINE_DICTIONARY_KEYS=False, 205 BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF=False, 206 BLANK_LINE_BEFORE_CLASS_DOCSTRING=False, 207 COALESCE_BRACKETS=False, 208 COLUMN_LIMIT=79, 209 CONTINUATION_INDENT_WIDTH=4, 210 DEDENT_CLOSING_BRACKETS=False, 211 EACH_DICT_ENTRY_ON_SEPARATE_LINE=True, 212 I18N_COMMENT='', 213 I18N_FUNCTION_CALL='', 214 INDENT_DICTIONARY_VALUE=False, 215 INDENT_WIDTH=4, 216 JOIN_MULTIPLE_LINES=True, 217 SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=True, 218 SPACES_AROUND_POWER_OPERATOR=False, 219 SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=False, 220 SPACES_BEFORE_COMMENT=2, 221 SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED=False, 222 SPLIT_BEFORE_BITWISE_OPERATOR=False, 223 SPLIT_BEFORE_DICT_SET_GENERATOR=True, 224 SPLIT_BEFORE_FIRST_ARGUMENT=False, 225 SPLIT_BEFORE_LOGICAL_OPERATOR=False, 226 SPLIT_BEFORE_NAMED_ASSIGNS=True, 227 SPLIT_PENALTY_AFTER_OPENING_BRACKET=30, 228 SPLIT_PENALTY_AFTER_UNARY_OPERATOR=10000, 229 SPLIT_PENALTY_BEFORE_IF_EXPR=0, 230 SPLIT_PENALTY_BITWISE_OPERATOR=300, 231 SPLIT_PENALTY_EXCESS_CHARACTER=4500, 232 SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT=30, 233 SPLIT_PENALTY_IMPORT_NAMES=0, 234 SPLIT_PENALTY_LOGICAL_OPERATOR=300, 235 USE_TABS=False,) 236 237 238def CreateGoogleStyle(): 239 style = CreatePEP8Style() 240 style['ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT'] = False 241 style['BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF'] = True 242 style['COLUMN_LIMIT'] = 80 243 style['INDENT_WIDTH'] = 4 244 style['I18N_COMMENT'] = r'#\..*' 245 style['I18N_FUNCTION_CALL'] = ['N_', '_'] 246 style['SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET'] = False 247 return style 248 249 250def CreateChromiumStyle(): 251 style = CreateGoogleStyle() 252 style['ALLOW_MULTILINE_DICTIONARY_KEYS'] = True 253 style['INDENT_DICTIONARY_VALUE'] = True 254 style['INDENT_WIDTH'] = 2 255 style['JOIN_MULTIPLE_LINES'] = False 256 style['SPLIT_BEFORE_BITWISE_OPERATOR'] = True 257 return style 258 259 260def CreateFacebookStyle(): 261 style = CreatePEP8Style() 262 style['ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT'] = False 263 style['COLUMN_LIMIT'] = 80 264 style['DEDENT_CLOSING_BRACKETS'] = True 265 style['JOIN_MULTIPLE_LINES'] = False 266 style['SPACES_BEFORE_COMMENT'] = 2 267 style['SPLIT_PENALTY_AFTER_OPENING_BRACKET'] = 0 268 style['SPLIT_PENALTY_BEFORE_IF_EXPR'] = 30 269 style['SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT'] = 30 270 return style 271 272 273_STYLE_NAME_TO_FACTORY = dict( 274 pep8=CreatePEP8Style, 275 chromium=CreateChromiumStyle, 276 google=CreateGoogleStyle, 277 facebook=CreateFacebookStyle,) 278 279_DEFAULT_STYLE_TO_FACTORY = [ 280 (CreateChromiumStyle(), CreateChromiumStyle), 281 (CreateFacebookStyle(), CreateFacebookStyle), 282 (CreateGoogleStyle(), CreateGoogleStyle), 283 (CreatePEP8Style(), CreatePEP8Style), 284] 285 286 287def _GetStyleFactory(style): 288 for def_style, factory in _DEFAULT_STYLE_TO_FACTORY: 289 if style == def_style: 290 return factory 291 return None 292 293 294def _StringListConverter(s): 295 """Option value converter for a comma-separated list of strings.""" 296 return [part.strip() for part in s.split(',')] 297 298 299def _BoolConverter(s): 300 """Option value converter for a boolean.""" 301 return py3compat.CONFIGPARSER_BOOLEAN_STATES[s.lower()] 302 303 304# Different style options need to have their values interpreted differently when 305# read from the config file. This dict maps an option name to a "converter" 306# function that accepts the string read for the option's value from the file and 307# returns it wrapper in actual Python type that's going to be meaningful to 308# yapf. 309# 310# Note: this dict has to map all the supported style options. 311_STYLE_OPTION_VALUE_CONVERTER = dict( 312 ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT=_BoolConverter, 313 ALLOW_MULTILINE_LAMBDAS=_BoolConverter, 314 ALLOW_MULTILINE_DICTIONARY_KEYS=_BoolConverter, 315 BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF=_BoolConverter, 316 BLANK_LINE_BEFORE_CLASS_DOCSTRING=_BoolConverter, 317 COALESCE_BRACKETS=_BoolConverter, 318 COLUMN_LIMIT=int, 319 CONTINUATION_INDENT_WIDTH=int, 320 DEDENT_CLOSING_BRACKETS=_BoolConverter, 321 EACH_DICT_ENTRY_ON_SEPARATE_LINE=_BoolConverter, 322 I18N_COMMENT=str, 323 I18N_FUNCTION_CALL=_StringListConverter, 324 INDENT_DICTIONARY_VALUE=_BoolConverter, 325 INDENT_WIDTH=int, 326 JOIN_MULTIPLE_LINES=_BoolConverter, 327 SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=_BoolConverter, 328 SPACES_AROUND_POWER_OPERATOR=_BoolConverter, 329 SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=_BoolConverter, 330 SPACES_BEFORE_COMMENT=int, 331 SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED=_BoolConverter, 332 SPLIT_BEFORE_BITWISE_OPERATOR=_BoolConverter, 333 SPLIT_BEFORE_DICT_SET_GENERATOR=_BoolConverter, 334 SPLIT_BEFORE_FIRST_ARGUMENT=_BoolConverter, 335 SPLIT_BEFORE_LOGICAL_OPERATOR=_BoolConverter, 336 SPLIT_BEFORE_NAMED_ASSIGNS=_BoolConverter, 337 SPLIT_PENALTY_AFTER_OPENING_BRACKET=int, 338 SPLIT_PENALTY_AFTER_UNARY_OPERATOR=int, 339 SPLIT_PENALTY_BEFORE_IF_EXPR=int, 340 SPLIT_PENALTY_BITWISE_OPERATOR=int, 341 SPLIT_PENALTY_EXCESS_CHARACTER=int, 342 SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT=int, 343 SPLIT_PENALTY_IMPORT_NAMES=int, 344 SPLIT_PENALTY_LOGICAL_OPERATOR=int, 345 USE_TABS=_BoolConverter,) 346 347 348def CreateStyleFromConfig(style_config): 349 """Create a style dict from the given config. 350 351 Arguments: 352 style_config: either a style name or a file name. The file is expected to 353 contain settings. It can have a special BASED_ON_STYLE setting naming the 354 style which it derives from. If no such setting is found, it derives from 355 the default style. When style_config is None, the _GLOBAL_STYLE_FACTORY 356 config is created. 357 358 Returns: 359 A style dict. 360 361 Raises: 362 StyleConfigError: if an unknown style option was encountered. 363 """ 364 365 def GlobalStyles(): 366 for style, _ in _DEFAULT_STYLE_TO_FACTORY: 367 yield style 368 369 def_style = False 370 if style_config is None: 371 for style in GlobalStyles(): 372 if _style == style: 373 def_style = True 374 break 375 if not def_style: 376 return _style 377 return _GLOBAL_STYLE_FACTORY() 378 style_factory = _STYLE_NAME_TO_FACTORY.get(style_config.lower()) 379 if style_factory is not None: 380 return style_factory() 381 if style_config.startswith('{'): 382 # Most likely a style specification from the command line. 383 config = _CreateConfigParserFromConfigString(style_config) 384 else: 385 # Unknown config name: assume it's a file name then. 386 config = _CreateConfigParserFromConfigFile(style_config) 387 return _CreateStyleFromConfigParser(config) 388 389 390def _CreateConfigParserFromConfigString(config_string): 391 """Given a config string from the command line, return a config parser.""" 392 if config_string[0] != '{' or config_string[-1] != '}': 393 raise StyleConfigError( 394 "Invalid style dict syntax: '{}'.".format(config_string)) 395 config = py3compat.ConfigParser() 396 config.add_section('style') 397 for key, value in re.findall(r'([a-zA-Z0-9_]+)\s*[:=]\s*([a-zA-Z0-9_]+)', 398 config_string): 399 config.set('style', key, value) 400 return config 401 402 403def _CreateConfigParserFromConfigFile(config_filename): 404 """Read the file and return a ConfigParser object.""" 405 if not os.path.exists(config_filename): 406 # Provide a more meaningful error here. 407 raise StyleConfigError( 408 '"{0}" is not a valid style or file path'.format(config_filename)) 409 with open(config_filename) as style_file: 410 config = py3compat.ConfigParser() 411 config.read_file(style_file) 412 if config_filename.endswith(SETUP_CONFIG): 413 if not config.has_section('yapf'): 414 raise StyleConfigError( 415 'Unable to find section [yapf] in {0}'.format(config_filename)) 416 elif config_filename.endswith(LOCAL_STYLE): 417 if not config.has_section('style'): 418 raise StyleConfigError( 419 'Unable to find section [style] in {0}'.format(config_filename)) 420 else: 421 if not config.has_section('style'): 422 raise StyleConfigError( 423 'Unable to find section [style] in {0}'.format(config_filename)) 424 return config 425 426 427def _CreateStyleFromConfigParser(config): 428 """Create a style dict from a configuration file. 429 430 Arguments: 431 config: a ConfigParser object. 432 433 Returns: 434 A style dict. 435 436 Raises: 437 StyleConfigError: if an unknown style option was encountered. 438 """ 439 # Initialize the base style. 440 section = 'yapf' if config.has_section('yapf') else 'style' 441 if config.has_option('style', 'based_on_style'): 442 based_on = config.get('style', 'based_on_style').lower() 443 base_style = _STYLE_NAME_TO_FACTORY[based_on]() 444 elif config.has_option('yapf', 'based_on_style'): 445 based_on = config.get('yapf', 'based_on_style').lower() 446 base_style = _STYLE_NAME_TO_FACTORY[based_on]() 447 else: 448 base_style = _GLOBAL_STYLE_FACTORY() 449 450 # Read all options specified in the file and update the style. 451 for option, value in config.items(section): 452 if option.lower() == 'based_on_style': 453 # Now skip this one - we've already handled it and it's not one of the 454 # recognized style options. 455 continue 456 option = option.upper() 457 if option not in _STYLE_OPTION_VALUE_CONVERTER: 458 raise StyleConfigError('Unknown style option "{0}"'.format(option)) 459 try: 460 base_style[option] = _STYLE_OPTION_VALUE_CONVERTER[option](value) 461 except ValueError: 462 raise StyleConfigError( 463 "'{}' is not a valid setting for {}.".format(value, option)) 464 return base_style 465 466 467# The default style - used if yapf is not invoked without specifically 468# requesting a formatting style. 469DEFAULT_STYLE = 'pep8' 470DEFAULT_STYLE_FACTORY = CreatePEP8Style 471_GLOBAL_STYLE_FACTORY = CreatePEP8Style 472 473# The name of the file to use for global style definition. 474GLOBAL_STYLE = (os.path.join( 475 os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'yapf', 476 'style')) 477 478# The name of the file to use for directory-local style definition. 479LOCAL_STYLE = '.style.yapf' 480 481# Alternative place for directory-local style definition. Style should be 482# specified in the '[yapf]' section. 483SETUP_CONFIG = 'setup.cfg' 484 485# TODO(eliben): For now we're preserving the global presence of a style dict. 486# Refactor this so that the style is passed around through yapf rather than 487# being global. 488_style = None 489SetGlobalStyle(_GLOBAL_STYLE_FACTORY()) 490