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 28EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS = ('.md', '.html') 29 30def _ReadFile(filename): 31 with open(filename) as f: 32 return f.read() 33 34def _ListFilesInPublic(): 35 all_files = [] 36 for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH): 37 all_files.extend( 38 os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):] 39 for filename in files) 40 return all_files 41 42def _UnixName(name): 43 name = os.path.splitext(name)[0] 44 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name) 45 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1) 46 return s2.replace('.', '_').lower() 47 48def _FindMatchingTemplates(template_name, template_path_list): 49 matches = [] 50 unix_name = _UnixName(template_name) 51 for template in template_path_list: 52 if unix_name == _UnixName(template.split(os.sep)[-1]): 53 basename, ext = os.path.splitext(template) 54 # The docserver expects clean (extensionless) template URLs, so we 55 # strip some extensions here when generating the list of matches. 56 if ext in EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS: 57 matches.append(basename) 58 else: 59 matches.append(template) 60 return matches 61 62def _SanitizeAPIName(name, api_path): 63 if not api_path.endswith(os.sep): 64 api_path += os.sep 65 filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_') 66 if 'experimental' in filename: 67 filename = 'experimental_' + filename.replace('experimental_', '') 68 return filename 69 70def _CreateIntegrationTestArgs(affected_files): 71 if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH) 72 for name in affected_files) or 73 any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH) 74 for name in affected_files)): 75 return ['-a'] 76 args = [] 77 for name in affected_files: 78 if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or 79 fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or 80 fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)): 81 args.extend(_FindMatchingTemplates(name.split(os.sep)[-1], 82 _ListFilesInPublic())) 83 if fnmatch.fnmatch(name, '%s*' % API_PATH): 84 args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH), 85 _ListFilesInPublic())) 86 return args 87 88def _CheckHeadingIDs(input_api): 89 ids_re = re.compile('<h[23].*id=.*?>') 90 headings_re = re.compile('<h[23].*?>') 91 bad_files = [] 92 for name in input_api.AbsoluteLocalPaths(): 93 if not os.path.exists(name): 94 continue 95 if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or 96 fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)): 97 contents = input_api.ReadFile(name) 98 if (len(re.findall(headings_re, contents)) != 99 len(re.findall(ids_re, contents))): 100 bad_files.append(name) 101 return bad_files 102 103def _CheckLinks(input_api, output_api, results): 104 for affected_file in input_api.AffectedFiles(): 105 name = affected_file.LocalPath() 106 absolute_path = affected_file.AbsoluteLocalPath() 107 if not os.path.exists(absolute_path): 108 continue 109 if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or 110 fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or 111 fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or 112 fnmatch.fnmatch(name, '%s*' % API_PATH)): 113 contents = _ReadFile(absolute_path) 114 args = [] 115 if input_api.platform == 'win32': 116 args = [input_api.python_executable] 117 args.extend([os.path.join('docs', 'server2', 'link_converter.py'), 118 '-o', 119 '-f', 120 absolute_path]) 121 output = input_api.subprocess.check_output( 122 args, 123 cwd=input_api.PresubmitLocalPath(), 124 universal_newlines=True) 125 if output != contents: 126 changes = '' 127 for i, (line1, line2) in enumerate( 128 zip(contents.split('\n'), output.split('\n'))): 129 if line1 != line2: 130 changes = ('%s\nLine %d:\n-%s\n+%s\n' % 131 (changes, i + 1, line1, line2)) 132 if changes: 133 results.append(output_api.PresubmitPromptWarning( 134 'File %s may have an old-style <a> link to an API page. Please ' 135 'run docs/server2/link_converter.py to convert the link[s], or ' 136 'convert them manually.\n\nSuggested changes are: %s' % 137 (name, changes))) 138 139def _CheckChange(input_api, output_api): 140 results = [ 141 output_api.PresubmitError('File %s needs an id for each heading.' % name) 142 for name in _CheckHeadingIDs(input_api)] 143 try: 144 integration_test = [] 145 # From depot_tools/presubmit_canned_checks.py:529 146 if input_api.platform == 'win32': 147 integration_test = [input_api.python_executable] 148 integration_test.append( 149 os.path.join('docs', 'server2', 'integration_test.py')) 150 integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths())) 151 input_api.subprocess.check_call(integration_test, 152 cwd=input_api.PresubmitLocalPath()) 153 except input_api.subprocess.CalledProcessError: 154 results.append(output_api.PresubmitError('IntegrationTest failed!')) 155 156 # TODO(kalman): Re-enable this check, or decide to delete it forever. Now 157 # that we have multiple directories it no longer works. 158 # See http://crbug.com/297178. 159 #_CheckLinks(input_api, output_api, results) 160 161 return results 162 163def CheckChangeOnUpload(input_api, output_api): 164 results = [] 165 results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api) 166 results += _CheckChange(input_api, output_api) 167 return results 168 169def CheckChangeOnCommit(input_api, output_api): 170 return _CheckChange(input_api, output_api) 171