# Copyright (c) 2012 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. """Presubmit script for changes affecting extensions. See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for more details about the presubmit API built into gcl. """ import fnmatch import os import re EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions') DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs') SERVER2_PATH = os.path.join(DOCS_PATH, 'server2') API_PATH = os.path.join(EXTENSIONS_PATH, 'api') TEMPLATES_PATH = os.path.join(DOCS_PATH, 'templates') PRIVATE_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'private') PUBLIC_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'public') INTROS_PATH = os.path.join(TEMPLATES_PATH, 'intros') ARTICLES_PATH = os.path.join(TEMPLATES_PATH, 'articles') LOCAL_PUBLIC_TEMPLATES_PATH = os.path.join('docs', 'templates', 'public') EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS = ('.md', '.html') def _ReadFile(filename): with open(filename) as f: return f.read() def _ListFilesInPublic(): all_files = [] for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH): all_files.extend( os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):] for filename in files) return all_files def _UnixName(name): name = os.path.splitext(name)[0] s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name) s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1) return s2.replace('.', '_').lower() def _FindMatchingTemplates(template_name, template_path_list): matches = [] unix_name = _UnixName(template_name) for template in template_path_list: if unix_name == _UnixName(template.split(os.sep)[-1]): basename, ext = os.path.splitext(template) # The docserver expects clean (extensionless) template URLs, so we # strip some extensions here when generating the list of matches. if ext in EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS: matches.append(basename) else: matches.append(template) return matches def _SanitizeAPIName(name, api_path): if not api_path.endswith(os.sep): api_path += os.sep filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_') if 'experimental' in filename: filename = 'experimental_' + filename.replace('experimental_', '') return filename def _CreateIntegrationTestArgs(affected_files): if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH) for name in affected_files) or any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH) for name in affected_files)): return ['-a'] args = [] for name in affected_files: if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)): args.extend(_FindMatchingTemplates(name.split(os.sep)[-1], _ListFilesInPublic())) if fnmatch.fnmatch(name, '%s*' % API_PATH): args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH), _ListFilesInPublic())) return args def _CheckHeadingIDs(input_api): ids_re = re.compile('<h[23].*id=.*?>') headings_re = re.compile('<h[23].*?>') bad_files = [] for name in input_api.AbsoluteLocalPaths(): if not os.path.exists(name): continue if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)): contents = input_api.ReadFile(name) if (len(re.findall(headings_re, contents)) != len(re.findall(ids_re, contents))): bad_files.append(name) return bad_files def _CheckLinks(input_api, output_api, results): for affected_file in input_api.AffectedFiles(): name = affected_file.LocalPath() absolute_path = affected_file.AbsoluteLocalPath() if not os.path.exists(absolute_path): continue if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or fnmatch.fnmatch(name, '%s*' % API_PATH)): contents = _ReadFile(absolute_path) args = [] if input_api.platform == 'win32': args = [input_api.python_executable] args.extend([os.path.join('docs', 'server2', 'link_converter.py'), '-o', '-f', absolute_path]) output = input_api.subprocess.check_output( args, cwd=input_api.PresubmitLocalPath(), universal_newlines=True) if output != contents: changes = '' for i, (line1, line2) in enumerate( zip(contents.split('\n'), output.split('\n'))): if line1 != line2: changes = ('%s\nLine %d:\n-%s\n+%s\n' % (changes, i + 1, line1, line2)) if changes: results.append(output_api.PresubmitPromptWarning( 'File %s may have an old-style <a> link to an API page. Please ' 'run docs/server2/link_converter.py to convert the link[s], or ' 'convert them manually.\n\nSuggested changes are: %s' % (name, changes))) def _CheckChange(input_api, output_api): results = [ output_api.PresubmitError('File %s needs an id for each heading.' % name) for name in _CheckHeadingIDs(input_api)] try: integration_test = [] # From depot_tools/presubmit_canned_checks.py:529 if input_api.platform == 'win32': integration_test = [input_api.python_executable] integration_test.append( os.path.join('docs', 'server2', 'integration_test.py')) integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths())) input_api.subprocess.check_call(integration_test, cwd=input_api.PresubmitLocalPath()) except input_api.subprocess.CalledProcessError: results.append(output_api.PresubmitError('IntegrationTest failed!')) # TODO(kalman): Re-enable this check, or decide to delete it forever. Now # that we have multiple directories it no longer works. # See http://crbug.com/297178. #_CheckLinks(input_api, output_api, results) return results def CheckChangeOnUpload(input_api, output_api): results = [] results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api) results += _CheckChange(input_api, output_api) return results def CheckChangeOnCommit(input_api, output_api): return _CheckChange(input_api, output_api)