aboutsummaryrefslogtreecommitdiff
path: root/tools/fix_style.py
blob: e0debaeff8d345c56bcb607d7cb25898c32c8a21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# Copyright (c) 2017 The Chromium Embedded Framework Authors.
# Portions copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import absolute_import
from __future__ import print_function
import os, re, sys
from clang_util import clang_format
from file_util import eval_file, get_files, read_file, write_file
from git_util import get_changed_files
from yapf_util import yapf_format

# File extensions that can be formatted.
DEFAULT_LINT_WHITELIST_REGEX = r"(.*\.cpp|.*\.cc|.*\.h|.*\.java|.*\.mm|.*\.py)$"
DEFAULT_LINT_BLACKLIST_REGEX = r"$^"

# Directories containing these path components will be ignored.
IGNORE_DIRECTORIES = []

# Script directory.
script_dir = os.path.dirname(__file__)
root_dir = os.path.join(script_dir, os.pardir)


def msg(filename, status):
  if sys.platform == 'win32':
    # Use Unix path separator.
    filename = filename.replace("\\", "/")

  if len(filename) > 60:
    # Truncate the file path in a nice way.
    filename = filename[-57:]
    pos = filename.find("/")
    if pos > 0:
      filename = filename[pos:]
    filename = "..." + filename

  print("%-60s %s" % (filename, status))


updatect = 0


def read_config():
  style_cfg = os.path.join(root_dir, ".style.cfg")
  if os.path.exists(style_cfg):
    config = eval_file(style_cfg)
    if 'ignore_directories' in config:
      global IGNORE_DIRECTORIES
      IGNORE_DIRECTORIES = config['ignore_directories']


def update_file(filename):
  oldcontents = read_file(filename)
  if len(oldcontents) == 0:
    msg(filename, "empty")
    return

  if os.path.splitext(filename)[1] == ".py":
    # Format Python files using YAPF.
    newcontents = yapf_format(filename, oldcontents)
  else:
    # Format C/C++/ObjC/Java files using clang-format.
    newcontents = clang_format(filename, oldcontents)

  if newcontents is None:
    raise Exception("Failed to process %s" % filename)

  if newcontents != oldcontents:
    msg(filename, "fixed")
    global updatect
    updatect += 1
    write_file(filename, newcontents)
  else:
    msg(filename, "ok")
  return


def fix_style(filenames, white_list=None, black_list=None):
  """ Execute clang-format with the specified arguments. """
  if not white_list:
    white_list = DEFAULT_LINT_WHITELIST_REGEX
  white_regex = re.compile(white_list)
  if not black_list:
    black_list = DEFAULT_LINT_BLACKLIST_REGEX
  black_regex = re.compile(black_list)

  for filename in filenames:
    # Ignore files from specific directories.
    ignore = False
    for dir_part in filename.split(os.sep):
      if dir_part in IGNORE_DIRECTORIES:
        msg(filename, "ignored")
        ignore = True
        break
    if ignore:
      continue

    if filename.find('*') > 0:
      # Expand wildcards.
      filenames.extend(get_files(filename))
      continue

    if os.path.isdir(filename):
      # Add directory contents.
      filenames.extend(get_files(os.path.join(filename, "*")))
      continue

    if not os.path.exists(filename):
      files = get_changed_files(".", filename)
      if len(files) > 0:
        filenames.extend(files)
      else:
        msg(filename, "missing")
      continue

    if white_regex.match(filename):
      if black_regex.match(filename):
        msg(filename, "ignored")
      else:
        update_file(filename)
    else:
      msg(filename, "skipped")


if __name__ == "__main__":
  if len(sys.argv) == 1:
    print("Usage: %s [file-path|git-hash|unstaged|staged] ...\n" % sys.argv[0])
    print(" Format C, C++ and ObjC files using Chromium's clang-format style.")
    print("\nOptions:")
    print(" file-path\tProcess the specified file or directory.")
    print(" \t\tDirectories will be processed recursively.")
    print(" \t\tThe \"*\" wildcard character is supported.")
    print(" git-hash\tProcess all files changed in the specified Git commit.")
    print(" unstaged\tProcess all unstaged files in the Git repo.")
    print(" staged\t\tProcess all staged files in the Git repo.")
    sys.exit(1)

  # Read the configuration file.
  read_config()

  # Process anything passed on the command-line.
  fix_style(sys.argv[1:])
  print('Done - Wrote %d files.' % updatect)