PRESUBMIT.py revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Presubmit script for changes affecting extensions.
6
7See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8for more details about the presubmit API built into gcl.
9"""
10import fnmatch
11import os
12import re
13
14EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
15DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
16SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
17API_PATH = os.path.join(EXTENSIONS_PATH, 'api')
18TEMPLATES_PATH = os.path.join(DOCS_PATH, 'templates')
19PRIVATE_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'private')
20PUBLIC_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'public')
21INTROS_PATH = os.path.join(TEMPLATES_PATH, 'intros')
22ARTICLES_PATH = os.path.join(TEMPLATES_PATH, 'articles')
23
24LOCAL_PUBLIC_TEMPLATES_PATH = os.path.join('docs',
25                                           'templates',
26                                           'public')
27
28def _ReadFile(filename):
29  with open(filename) as f:
30    return f.read()
31
32def _ListFilesInPublic():
33  all_files = []
34  for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH):
35    all_files.extend(
36        os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):]
37        for filename in files)
38  return all_files
39
40def _UnixName(name):
41  name = os.path.splitext(name)[0]
42  s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
43  s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
44  return s2.replace('.', '_').lower()
45
46def _FindMatchingTemplates(template_name, template_path_list):
47  matches = []
48  unix_name = _UnixName(template_name)
49  for template in template_path_list:
50    if unix_name == _UnixName(template.split(os.sep)[-1]):
51      matches.append(template)
52  return matches
53
54def _SanitizeAPIName(name, api_path):
55  if not api_path.endswith(os.sep):
56    api_path += os.sep
57  filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_')
58  if 'experimental' in filename:
59    filename = 'experimental_' + filename.replace('experimental_', '')
60  return filename
61
62def _CreateIntegrationTestArgs(affected_files):
63  if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH)
64         for name in affected_files) or
65      any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH)
66          for name in affected_files)):
67    return ['-a']
68  args = []
69  for name in affected_files:
70    if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
71        fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
72        fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)):
73      args.extend(_FindMatchingTemplates(name.split(os.sep)[-1],
74                                         _ListFilesInPublic()))
75    if fnmatch.fnmatch(name, '%s*' % API_PATH):
76      args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH),
77                                         _ListFilesInPublic()))
78  return args
79
80def _CheckHeadingIDs(input_api):
81  ids_re = re.compile('<h[23].*id=.*?>')
82  headings_re = re.compile('<h[23].*?>')
83  bad_files = []
84  for name in input_api.AbsoluteLocalPaths():
85    if not os.path.exists(name):
86      continue
87    if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or
88        fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)):
89      contents = input_api.ReadFile(name)
90      if (len(re.findall(headings_re, contents)) !=
91          len(re.findall(ids_re, contents))):
92        bad_files.append(name)
93  return bad_files
94
95def _CheckLinks(input_api, output_api, results):
96  for affected_file in input_api.AffectedFiles():
97    name = affected_file.LocalPath()
98    absolute_path = affected_file.AbsoluteLocalPath()
99    if not os.path.exists(absolute_path):
100      continue
101    if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
102        fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
103        fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or
104        fnmatch.fnmatch(name, '%s*' % API_PATH)):
105      contents = _ReadFile(absolute_path)
106      args = []
107      if input_api.platform == 'win32':
108        args = [input_api.python_executable]
109      args.extend([os.path.join('docs', 'server2', 'link_converter.py'),
110                   '-o',
111                   '-f',
112                   absolute_path])
113      output = input_api.subprocess.check_output(
114          args,
115          cwd=input_api.PresubmitLocalPath(),
116          universal_newlines=True)
117      if output != contents:
118        changes = ''
119        for i, (line1, line2) in enumerate(
120            zip(contents.split('\n'), output.split('\n'))):
121          if line1 != line2:
122            changes = ('%s\nLine %d:\n-%s\n+%s\n' %
123                (changes, i + 1, line1, line2))
124        if changes:
125          results.append(output_api.PresubmitPromptWarning(
126              'File %s may have an old-style <a> link to an API page. Please '
127              'run docs/server2/link_converter.py to convert the link[s], or '
128              'convert them manually.\n\nSuggested changes are: %s' %
129              (name, changes)))
130
131def _CheckChange(input_api, output_api):
132  results = [
133      output_api.PresubmitError('File %s needs an id for each heading.' % name)
134      for name in _CheckHeadingIDs(input_api)]
135  try:
136    integration_test = []
137    # From depot_tools/presubmit_canned_checks.py:529
138    if input_api.platform == 'win32':
139      integration_test = [input_api.python_executable]
140    integration_test.append(
141        os.path.join('docs', 'server2', 'integration_test.py'))
142    integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths()))
143    input_api.subprocess.check_call(integration_test,
144                                    cwd=input_api.PresubmitLocalPath())
145  except input_api.subprocess.CalledProcessError:
146    results.append(output_api.PresubmitError('IntegrationTest failed!'))
147  _CheckLinks(input_api, output_api, results)
148  return results
149
150def CheckChangeOnUpload(input_api, output_api):
151  return _CheckChange(input_api, output_api)
152
153def CheckChangeOnCommit(input_api, output_api):
154  return _CheckChange(input_api, output_api)
155