1#!/usr/bin/env python
2# Copyright 2018 the V8 project authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6# for py2/py3 compatibility
7from __future__ import print_function
8
9import argparse
10from datetime import datetime
11import re
12import subprocess
13import sys
14
15RE_GITHASH = re.compile(r"^[0-9a-f]{40}")
16RE_AUTHOR_TIME = re.compile(r"^author-time (\d+)$")
17RE_FILENAME = re.compile(r"^filename (.+)$")
18
19def GetBlame(file_path):
20  result = subprocess.check_output(
21      ['git', 'blame', '-t', '--line-porcelain', file_path])
22  line_iter = iter(result.splitlines())
23  blame_list = list()
24  current_blame = None
25  while True:
26    line = next(line_iter, None)
27    if line is None:
28      break
29    if RE_GITHASH.match(line):
30      if current_blame is not None:
31        blame_list.append(current_blame)
32      current_blame = {'time': 0, 'filename': None, 'content': None}
33      continue
34    match = RE_AUTHOR_TIME.match(line)
35    if match:
36      current_blame['time'] = datetime.fromtimestamp(int(match.groups()[0]))
37      continue
38    match = RE_FILENAME.match(line)
39    if match:
40      current_blame['filename'] = match.groups()[0]
41      current_blame['content'] = next(line_iter).strip()
42      continue
43  blame_list.append(current_blame)
44  return blame_list
45
46RE_MACRO_END = re.compile(r"\);");
47RE_DEPRECATE_MACRO = re.compile(r"\(.*?,(.*)\);", re.MULTILINE)
48
49def FilterAndPrint(blame_list, macro, before):
50  index = 0
51  re_macro = re.compile(macro)
52  deprecated = list()
53  while index < len(blame_list):
54    blame = blame_list[index]
55    match = re_macro.search(blame['content'])
56    if match and blame['time'] < before:
57      line = blame['content']
58      time = blame['time']
59      pos = match.end()
60      start = -1
61      parens = 0
62      quotes = 0
63      while True:
64        if pos >= len(line):
65          # extend to next line
66          index = index + 1
67          blame = blame_list[index]
68          if line.endswith(','):
69            # add whitespace when breaking line due to comma
70            line = line + ' '
71          line = line + blame['content']
72        if line[pos] == '(':
73          parens = parens + 1
74        elif line[pos] == ')':
75          parens = parens - 1
76          if parens == 0:
77            break
78        elif line[pos] == '"':
79          quotes = quotes + 1
80        elif line[pos] == ',' and quotes % 2 == 0 and start == -1:
81          start = pos + 1
82        pos = pos + 1
83      deprecated.append([index + 1, time, line[start:pos].strip()])
84    index = index + 1
85  print("Marked as " + macro + ": " + str(len(deprecated)))
86  for linenumber, time, content in deprecated:
87    print(str(linenumber).rjust(8) + " : " + str(time) + " : " + content)
88  return len(deprecated)
89
90def ParseOptions(args):
91  parser = argparse.ArgumentParser(description="Collect deprecation statistics")
92  parser.add_argument("file_path", help="Path to v8.h")
93  parser.add_argument("--before", help="Filter by date")
94  options = parser.parse_args(args)
95  if options.before:
96    options.before = datetime.strptime(options.before, '%Y-%m-%d')
97  else:
98    options.before = datetime.now()
99  return options
100
101def Main(args):
102  options = ParseOptions(args)
103  blame_list = GetBlame(options.file_path)
104  FilterAndPrint(blame_list, "V8_DEPRECATE_SOON", options.before)
105  FilterAndPrint(blame_list, "V8_DEPRECATED", options.before)
106
107if __name__ == "__main__":
108  Main(sys.argv[1:])
109