示例#1
0
def _FormatFile(filename,
                lines,
                style_config=None,
                no_local_style=False,
                in_place=False,
                print_diff=False,
                verify=False,
                quiet=False,
                verbose=False):
    """Format an individual file."""
    if verbose and not quiet:
        print('Reformatting %s' % filename)
    if style_config is None and not no_local_style:
        style_config = file_resources.GetDefaultStyleForDir(
            os.path.dirname(filename))
    try:
        reformatted_code, encoding, has_change = yapf_api.FormatFile(
            filename,
            in_place=in_place,
            style_config=style_config,
            lines=lines,
            print_diff=print_diff,
            verify=verify,
            logger=logging.warning)
        if not in_place and not quiet and reformatted_code:
            file_resources.WriteReformattedCode(filename, reformatted_code,
                                                encoding, in_place)
        return has_change
    except tokenize.TokenError as e:
        raise errors.YapfError('%s:%s:%s' %
                               (filename, e.args[1][0], e.args[0]))
    except SyntaxError as e:
        e.filename = filename
        raise
示例#2
0
def _FindPythonFiles(filenames, recursive, exclude):
    """Find all Python files."""
    python_files = []
    for filename in filenames:
        if os.path.isdir(filename):
            if recursive:
                # TODO(morbo): Look into a version of os.walk that can handle recursion.
                python_files.extend(
                    os.path.join(dirpath, f)
                    for dirpath, _, filelist in os.walk(filename)
                    for f in filelist
                    if IsPythonFile(os.path.join(dirpath, f)))
            else:
                raise errors.YapfError(
                    "directory specified without '--recursive' flag: %s" %
                    filename)
        elif os.path.isfile(filename):
            python_files.append(filename)

    if exclude:
        return [
            f for f in python_files
            if not any(fnmatch.fnmatch(f, p) for p in exclude)
        ]

    return python_files
示例#3
0
def _FindPythonFiles(filenames, recursive, exclude):
    """Find all Python files."""
    if exclude and any(e.startswith('./') for e in exclude):
        raise errors.YapfError("path in '--exclude' should not start with ./")
    exclude = exclude and [e.rstrip("/" + os.path.sep) for e in exclude]

    python_files = []
    for filename in filenames:
        if filename != '.' and exclude and IsIgnored(filename, exclude):
            continue
        if os.path.isdir(filename):
            if not recursive:
                raise errors.YapfError(
                    "directory specified without '--recursive' flag: %s" %
                    filename)

            # TODO(morbo): Look into a version of os.walk that can handle recursion.
            excluded_dirs = []
            for dirpath, dirnames, filelist in os.walk(filename):
                if dirpath != '.' and exclude and IsIgnored(dirpath, exclude):
                    excluded_dirs.append(dirpath)
                    continue
                elif any(dirpath.startswith(e) for e in excluded_dirs):
                    continue
                for f in filelist:
                    filepath = os.path.join(dirpath, f)
                    if exclude and IsIgnored(filepath, exclude):
                        continue
                    if IsPythonFile(filepath):
                        python_files.append(filepath)
                # To prevent it from scanning the contents excluded folders, os.walk()
                # lets you amend its list of child dirs `dirnames`. These edits must be
                # made in-place instead of creating a modified copy of `dirnames`.
                # list.remove() is slow and list.pop() is a headache. Instead clear
                # `dirnames` then repopulate it.
                dirnames_ = [dirnames.pop(0) for i in range(len(dirnames))]
                for dirname in dirnames_:
                    dir_ = os.path.join(dirpath, dirname)
                    if IsIgnored(dir_, exclude):
                        excluded_dirs.append(dir_)
                    else:
                        dirnames.append(dirname)

        elif os.path.isfile(filename):
            python_files.append(filename)

    return python_files
示例#4
0
def _GetExcludePatternsFromPyprojectToml(filename):
    """Get a list of file patterns to ignore from pyproject.toml."""
    ignore_patterns = []
    try:
        import toml
    except ImportError:
        raise errors.YapfError(
            "toml package is needed for using pyproject.toml as a configuration file"
        )

    if os.path.isfile(filename) and os.access(filename, os.R_OK):
        pyproject_toml = toml.load(filename)
        ignore_patterns = pyproject_toml.get('tool',
                                             {}).get('yapfignore', {}).get(
                                                 'ignore_patterns', [])
        if any(e.startswith('./') for e in ignore_patterns):
            raise errors.YapfError(
                'path in pyproject.toml should not start with ./')

    return ignore_patterns
示例#5
0
def _GetExcludePatternsFromFile(filename):
  ignore_patterns = []
  # See if we have a .yapfignore file.
  if os.path.isfile(filename) and os.access(filename, os.R_OK):
    for line in open(filename, 'r').readlines():
      if line.strip() and not line.startswith('#'):
        ignore_patterns.append(line.strip())

    if any(e.startswith('./') for e in ignore_patterns):
      raise errors.YapfError('path in .yapfignore should not start with ./')

  return ignore_patterns
示例#6
0
def _GetLines(line_strings):
  """Parses the start and end lines from a line string like 'start-end'.

  Arguments:
    line_strings: (array of string) A list of strings representing a line
      range like 'start-end'.

  Returns:
    A list of tuples of the start and end line numbers.

  Raises:
    ValueError: If the line string failed to parse or was an invalid line range.
  """
  lines = []
  for line_string in line_strings:
    # The 'list' here is needed by Python 3.
    line = list(map(int, line_string.split('-', 1)))
    if line[0] < 1:
      raise errors.YapfError('invalid start of line range: %r' % line)
    if line[0] > line[1]:
      raise errors.YapfError('end comes before start in line range: %r', line)
    lines.append(tuple(line))
  return lines
示例#7
0
def _GetExcludePatternsFromYapfIgnore(filename):
    """Get a list of file patterns to ignore from .yapfignore."""
    ignore_patterns = []
    if os.path.isfile(filename) and os.access(filename, os.R_OK):
        with open(filename, 'r') as fd:
            for line in fd:
                if line.strip() and not line.startswith('#'):
                    ignore_patterns.append(line.strip())

        if any(e.startswith('./') for e in ignore_patterns):
            raise errors.YapfError(
                'path in .yapfignore should not start with ./')

    return ignore_patterns
示例#8
0
def FormatCode(unformatted_source,
               filename='<unknown>',
               style_config=None,
               lines=None,
               print_diff=False,
               verify=False):
    """Format a string of Python code.

  This provides an alternative entry point to YAPF.

  Arguments:
    unformatted_source: (unicode) The code to format.
    filename: (unicode) The name of the file being reformatted.
    style_config: (string) Either a style name or a path to a file that contains
      formatting style settings. If None is specified, use the default style
      as set in style.DEFAULT_STYLE_FACTORY
    lines: (list of tuples of integers) A list of tuples of lines, [start, end],
      that we want to format. The lines are 1-based indexed. It can be used by
      third-party code (e.g., IDEs) when reformatting a snippet of code rather
      than a whole file.
    print_diff: (bool) Instead of returning the reformatted source, return a
      diff that turns the formatted source into reformatter source.
    verify: (bool) True if reformatted code should be verified for syntax.

  Returns:
    Tuple of (reformatted_source, changed). reformatted_source conforms to the
    desired formatting style. changed is True if the source changed.
  """
    try:
        tree = pytree_utils.ParseCodeToTree(unformatted_source)
    except Exception as e:
        e.filename = filename
        raise errors.YapfError(errors.FormatErrorMsg(e))

    reformatted_source = FormatTree(tree,
                                    style_config=style_config,
                                    lines=lines,
                                    verify=verify)

    if unformatted_source == reformatted_source:
        return '' if print_diff else reformatted_source, False

    code_diff = _GetUnifiedDiff(unformatted_source,
                                reformatted_source,
                                filename=filename)

    if print_diff:
        return code_diff, code_diff.strip() != ''  # pylint: disable=g-explicit-bool-comparison # noqa

    return reformatted_source, True
示例#9
0
def _CreateConfigParserFromConfigFile(config_filename):
    """Read the file and return a ConfigParser object."""
    if not os.path.exists(config_filename):
        # Provide a more meaningful error here.
        raise StyleConfigError(
            '"{0}" is not a valid style or file path'.format(config_filename))
    with open(config_filename) as style_file:
        config = py3compat.ConfigParser()
        if config_filename.endswith(PYPROJECT_TOML):
            try:
                import toml
            except ImportError:
                raise errors.YapfError(
                    "toml package is needed for using pyproject.toml as a configuration file"
                )

            pyproject_toml = toml.load(style_file)
            style_dict = pyproject_toml.get("tool", {}).get("yapf", None)
            if style_dict is None:
                raise StyleConfigError(
                    'Unable to find section [tool.yapf] in {0}'.format(
                        config_filename))
            config.add_section('style')
            for k, v in style_dict.items():
                config.set('style', k, str(v))
            return config

        config.read_file(style_file)
        if config_filename.endswith(SETUP_CONFIG):
            if not config.has_section('yapf'):
                raise StyleConfigError(
                    'Unable to find section [yapf] in {0}'.format(
                        config_filename))
            return config

        if config_filename.endswith(LOCAL_STYLE):
            if not config.has_section('style'):
                raise StyleConfigError(
                    'Unable to find section [style] in {0}'.format(
                        config_filename))
            return config

        if not config.has_section('style'):
            raise StyleConfigError(
                'Unable to find section [style] in {0}'.format(
                    config_filename))
        return config
示例#10
0
def main(argv):
  """Main program.

  Arguments:
    argv: command-line arguments, such as sys.argv (including the program name
      in argv[0]).

  Returns:
    0 if there were no changes, non-zero otherwise.

  Raises:
    YapfError: if none of the supplied files were Python files.
  """
  parser = argparse.ArgumentParser(description='Formatter for Python code.')
  parser.add_argument(
      '-v',
      '--version',
      action='store_true',
      help='show version number and exit')

  diff_inplace_group = parser.add_mutually_exclusive_group()
  diff_inplace_group.add_argument(
      '-d',
      '--diff',
      action='store_true',
      help='print the diff for the fixed source')
  diff_inplace_group.add_argument(
      '-i',
      '--in-place',
      action='store_true',
      help='make changes to files in place')

  lines_recursive_group = parser.add_mutually_exclusive_group()
  lines_recursive_group.add_argument(
      '-r',
      '--recursive',
      action='store_true',
      help='run recursively over directories')
  lines_recursive_group.add_argument(
      '-l',
      '--lines',
      metavar='START-END',
      action='append',
      default=None,
      help='range of lines to reformat, one-based')

  parser.add_argument(
      '-e',
      '--exclude',
      metavar='PATTERN',
      action='append',
      default=None,
      help='patterns for files to exclude from formatting')
  parser.add_argument(
      '--style',
      action='store',
      help=('specify formatting style: either a style name (for example "pep8" '
            'or "google"), or the name of a file with style settings. The '
            'default is pep8 unless a %s or %s file located in one of the '
            'parent directories of the source file (or current directory for '
            'stdin)' % (style.LOCAL_STYLE, style.SETUP_CONFIG)))
  parser.add_argument(
      '--style-help',
      action='store_true',
      help=('show style settings and exit; this output can be '
            'saved to .style.yapf to make your settings '
            'permanent'))
  parser.add_argument(
      '--no-local-style',
      action='store_true',
      help="don't search for local style definition")
  parser.add_argument('--verify', action='store_true', help=argparse.SUPPRESS)
  parser.add_argument(
      '-p',
      '--parallel',
      action='store_true',
      help=('Run yapf in parallel when formatting multiple files. Requires '
            'concurrent.futures in Python 2.X'))

  parser.add_argument('files', nargs='*')
  args = parser.parse_args(argv[1:])

  if args.version:
    print('yapf {}'.format(__version__))
    return 0

  if args.style_help:
    style.SetGlobalStyle(style.CreateStyleFromConfig(args.style))
    print('[style]')
    for option, docstring in sorted(style.Help().items()):
      for line in docstring.splitlines():
        print('#', line and ' ' or '', line, sep='')
      print(option.lower(), '=', style.Get(option), sep='')
      print()
    return 0

  if args.lines and len(args.files) > 1:
    parser.error('cannot use -l/--lines with more than one file')

  lines = _GetLines(args.lines) if args.lines is not None else None
  if not args.files:
    # No arguments specified. Read code from stdin.
    if args.in_place or args.diff:
      parser.error('cannot use --in-place or --diff flags when reading '
                   'from stdin')

    original_source = []
    while True:
      try:
        # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the
        # user will need to hit 'Ctrl-D' more than once if they're inputting
        # the program by hand. 'raw_input' throws an EOFError exception if
        # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop.
        original_source.append(py3compat.raw_input())
      except EOFError:
        break

    style_config = args.style
    if style_config is None and not args.no_local_style:
      style_config = file_resources.GetDefaultStyleForDir(os.getcwd())

    source = [line.rstrip() for line in original_source]
    reformatted_source, _ = yapf_api.FormatCode(
        py3compat.unicode('\n'.join(source) + '\n'),
        filename='<stdin>',
        style_config=style_config,
        lines=lines,
        verify=args.verify)
    file_resources.WriteReformattedCode('<stdout>', reformatted_source)
    return 0

  files = file_resources.GetCommandLineFiles(args.files, args.recursive,
                                             args.exclude)
  if not files:
    raise errors.YapfError('Input filenames did not match any python files')

  FormatFiles(
      files,
      lines,
      style_config=args.style,
      no_local_style=args.no_local_style,
      in_place=args.in_place,
      print_diff=args.diff,
      verify=args.verify,
      parallel=args.parallel)
  return 0
示例#11
0
文件: __init__.py 项目: wwade/yapf
def main(argv):
    """Main program.

  Arguments:
    argv: command-line arguments, such as sys.argv (including the program name
      in argv[0]).

  Returns:
    Zero on successful program termination, non-zero otherwise.
    With --diff: zero if there were no changes, non-zero otherwise.

  Raises:
    YapfError: if none of the supplied files were Python files.
  """
    args = _ParseArguments(argv)
    if args.version:
        print('yapf {}'.format(__version__))
        return 0

    style_config = args.style

    if args.style_help:
        print_help(args)
        return 0

    if args.lines and len(args.files) > 1:
        parser.error('cannot use -l/--lines with more than one file')

    lines = _GetLines(args.lines) if args.lines is not None else None
    if not args.files:
        # No arguments specified. Read code from stdin.
        if args.in_place or args.diff:
            parser.error('cannot use --in-place or --diff flags when reading '
                         'from stdin')

        original_source = []
        while True:
            if sys.stdin.closed:
                break
            try:
                # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the
                # user will need to hit 'Ctrl-D' more than once if they're inputting
                # the program by hand. 'raw_input' throws an EOFError exception if
                # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop.
                original_source.append(py3compat.raw_input())
            except EOFError:
                break
            except KeyboardInterrupt:
                return 1

        if style_config is None and not args.no_local_style:
            style_config = file_resources.GetDefaultStyleForDir(os.getcwd())

        source = [line.rstrip() for line in original_source]
        source[0] = py3compat.removeBOM(source[0])

        try:
            reformatted_source, _ = yapf_api.FormatCode(
                py3compat.unicode('\n'.join(source) + '\n'),
                filename='<stdin>',
                style_config=style_config,
                lines=lines,
                verify=args.verify)
        except tokenize.TokenError as e:
            raise errors.YapfError('%s:%s' % (e.args[1][0], e.args[0]))

        file_resources.WriteReformattedCode('<stdout>', reformatted_source)
        return 0

    # Get additional exclude patterns from ignorefile
    exclude_patterns_from_ignore_file = file_resources.GetExcludePatternsForDir(
        os.getcwd())

    files = file_resources.GetCommandLineFiles(
        args.files, args.recursive,
        (args.exclude or []) + exclude_patterns_from_ignore_file)
    if not files:
        raise errors.YapfError(
            'Input filenames did not match any python files')

    changed = FormatFiles(files,
                          lines,
                          style_config=args.style,
                          no_local_style=args.no_local_style,
                          in_place=args.in_place,
                          print_diff=args.diff,
                          verify=args.verify,
                          parallel=args.parallel,
                          quiet=args.quiet,
                          verbose=args.verbose)
    return 1 if changed and (args.diff or args.quiet) else 0
示例#12
0
def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE):
    """Return default style name for a given directory.

  Looks for .style.yapf or setup.cfg or pyproject.toml in the parent directories.

  Arguments:
    dirname: (unicode) The name of the directory.
    default_style: The style to return if nothing is found. Defaults to the
                   global default style ('pep8') unless otherwise specified.

  Returns:
    The filename if found, otherwise return the default style.
  """
    dirname = os.path.abspath(dirname)
    while True:
        # See if we have a .style.yapf file.
        style_file = os.path.join(dirname, style.LOCAL_STYLE)
        if os.path.exists(style_file):
            return style_file

        # See if we have a setup.cfg file with a '[yapf]' section.
        config_file = os.path.join(dirname, style.SETUP_CONFIG)
        try:
            fd = open(config_file)
        except IOError:
            pass  # It's okay if it's not there.
        else:
            with fd:
                config = py3compat.ConfigParser()
                config.read_file(fd)
                if config.has_section('yapf'):
                    return config_file

        # See if we have a pyproject.toml file with a '[tool.yapf]'  section.
        config_file = os.path.join(dirname, style.PYPROJECT_TOML)
        try:
            fd = open(config_file)
        except IOError:
            pass  # It's okay if it's not there.
        else:
            with fd:
                try:
                    import toml
                except ImportError:
                    raise errors.YapfError(
                        "toml package is needed for using pyproject.toml as a configuration file"
                    )

                pyproject_toml = toml.load(config_file)
                style_dict = pyproject_toml.get('tool', {}).get('yapf', None)
                if style_dict is not None:
                    return config_file

        if (not dirname or not os.path.basename(dirname)
                or dirname == os.path.abspath(os.path.sep)):
            break
        dirname = os.path.dirname(dirname)

    global_file = os.path.expanduser(style.GLOBAL_STYLE)
    if os.path.exists(global_file):
        return global_file

    return default_style
示例#13
0
def main(argv):
  """Main program.

  Arguments:
    argv: command-line arguments, such as sys.argv (including the program name
      in argv[0]).

  Returns:
    Zero on successful program termination, non-zero otherwise.
    With --diff: zero if there were no changes, non-zero otherwise.

  Raises:
    YapfError: if none of the supplied files were Python files.
  """
  parser = argparse.ArgumentParser(description='Formatter for Python code.')
  parser.add_argument(
      '-v',
      '--version',
      action='store_true',
      help='show version number and exit')

  diff_inplace_quiet_group = parser.add_mutually_exclusive_group()
  diff_inplace_quiet_group.add_argument(
      '-d',
      '--diff',
      action='store_true',
      help='print the diff for the fixed source')
  diff_inplace_quiet_group.add_argument(
      '-i',
      '--in-place',
      action='store_true',
      help='make changes to files in place')
  diff_inplace_quiet_group.add_argument(
      '-q',
      '--quiet',
      action='store_true',
      help='output nothing and set return value')

  lines_recursive_group = parser.add_mutually_exclusive_group()
  lines_recursive_group.add_argument(
      '-r',
      '--recursive',
      action='store_true',
      help='run recursively over directories')
  lines_recursive_group.add_argument(
      '-l',
      '--lines',
      metavar='START-END',
      action='append',
      default=None,
      help='range of lines to reformat, one-based')

  parser.add_argument(
      '-e',
      '--exclude',
      metavar='PATTERN',
      action='append',
      default=None,
      help='patterns for files to exclude from formatting')
  parser.add_argument(
      '--style',
      action='store',
      help=('specify formatting style: either a style name (for example "pep8" '
            'or "google"), or the name of a file with style settings. The '
            'default is pep8 unless a %s or %s file located in the same '
            'directory as the source or one of its parent directories '
            '(for stdin, the current directory is used).' %
            (style.LOCAL_STYLE, style.SETUP_CONFIG)))
  parser.add_argument(
      '--style-help',
      action='store_true',
      help=('show style settings and exit; this output can be '
            'saved to .style.yapf to make your settings '
            'permanent'))
  parser.add_argument(
      '--no-local-style',
      action='store_true',
      help="don't search for local style definition")
  parser.add_argument('--verify', action='store_true', help=argparse.SUPPRESS)
  parser.add_argument(
      '-p',
      '--parallel',
      action='store_true',
      help=('run yapf in parallel when formatting multiple files. Requires '
            'concurrent.futures in Python 2.X'))
  parser.add_argument(
      '-vv',
      '--verbose',
      action='store_true',
      help='print out file names while processing')

  parser.add_argument(
      'files', nargs='*', help='reads from stdin when no files are specified.')
  args = parser.parse_args(argv[1:])

  if args.version:
    print('yapf {}'.format(__version__))
    return 0

  style_config = args.style

  if args.style_help:
    print_help(args)
    return 0

  if args.lines and len(args.files) > 1:
    parser.error('cannot use -l/--lines with more than one file')

  lines = _GetLines(args.lines) if args.lines is not None else None
  if not args.files:
    # No arguments specified. Read code from stdin.
    if args.in_place or args.diff:
      parser.error('cannot use --in-place or --diff flags when reading '
                   'from stdin')

    original_source = []
    while True:
      if sys.stdin.closed:
        break
      try:
        # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the
        # user will need to hit 'Ctrl-D' more than once if they're inputting
        # the program by hand. 'raw_input' throws an EOFError exception if
        # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop.
        original_source.append(py3compat.raw_input())
      except EOFError:
        break
      except KeyboardInterrupt:
        return 1

    if style_config is None and not args.no_local_style:
      style_config = file_resources.GetDefaultStyleForDir(os.getcwd())

    source = [line.rstrip() for line in original_source]
    source[0] = py3compat.removeBOM(source[0])

    try:
      reformatted_source, _ = yapf_api.FormatCode(
          py3compat.unicode('\n'.join(source) + '\n'),
          filename='<stdin>',
          style_config=style_config,
          lines=lines,
          verify=args.verify)
    except tokenize.TokenError as e:
      raise errors.YapfError('%s:%s' % (e.args[1][0], e.args[0]))

    file_resources.WriteReformattedCode('<stdout>', reformatted_source)
    return 0

  # Get additional exclude patterns from ignorefile
  exclude_patterns_from_ignore_file = file_resources.GetExcludePatternsForDir(
      os.getcwd())

  files = file_resources.GetCommandLineFiles(args.files, args.recursive,
                                             (args.exclude or []) +
                                             exclude_patterns_from_ignore_file)
  if not files:
    raise errors.YapfError('Input filenames did not match any python files')

  changed = FormatFiles(
      files,
      lines,
      style_config=args.style,
      no_local_style=args.no_local_style,
      in_place=args.in_place,
      print_diff=args.diff,
      verify=args.verify,
      parallel=args.parallel,
      quiet=args.quiet,
      verbose=args.verbose)
  return 1 if changed and (args.diff or args.quiet) else 0