• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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