diff options
author | V3n3RiX <venerix@redcorelinux.org> | 2018-04-20 19:27:12 +0100 |
---|---|---|
committer | V3n3RiX <venerix@redcorelinux.org> | 2018-04-20 19:27:12 +0100 |
commit | 56d0c8205c9fe7b4cff71030722e1a553aac006d (patch) | |
tree | dcde90176ac9f080bc5adeca3033a29c3900d593 /www-client/chromium/files | |
parent | b9e1b6165fc952c21eb1acde9a6d2d7ddbd2bc27 (diff) |
"www-client/chromium : add-missing-blink-tools, see : https://groups.google.com/a/chromium.org/forum/#!msg/chromium-packagers/So-ojMYOQdI/K66hndtdCAAJ"
Diffstat (limited to 'www-client/chromium/files')
-rw-r--r-- | www-client/chromium/files/add-missing-blink-tools.patch | 1071 |
1 files changed, 1071 insertions, 0 deletions
diff --git a/www-client/chromium/files/add-missing-blink-tools.patch b/www-client/chromium/files/add-missing-blink-tools.patch new file mode 100644 index 00000000..4eddd8c7 --- /dev/null +++ b/www-client/chromium/files/add-missing-blink-tools.patch @@ -0,0 +1,1071 @@ +Description: add back contents of third-party/blink/tools that went missing in the source tarball for 66.0.3359.106 +Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=832283 + +--- /dev/null ++++ b/third_party/blink/tools/OWNERS +@@ -0,0 +1 @@ ++file://third_party/WebKit/Tools/OWNERS +--- /dev/null ++++ b/third_party/blink/tools/blinkpy/__init__.py +@@ -0,0 +1,3 @@ ++# Copyright 2017 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. +--- /dev/null ++++ b/third_party/blink/tools/blinkpy/common/__init__.py +@@ -0,0 +1,3 @@ ++# Copyright 2017 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. +--- /dev/null ++++ b/third_party/blink/tools/blinkpy/common/name_style_converter.py +@@ -0,0 +1,128 @@ ++# Copyright 2017 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. ++ ++# pylint: disable=import-error,print-statement,relative-import ++ ++import re ++ ++SPECIAL_TOKENS = [ ++ # This list should be sorted by length. ++ 'CString', ++ 'Float32', ++ 'Float64', ++ 'Base64', ++ 'IFrame', ++ 'Latin1', ++ 'PlugIn', ++ 'SQLite', ++ 'Uint16', ++ 'Uint32', ++ 'WebGL2', ++ 'ASCII', ++ 'CType', ++ 'DList', ++ 'Int16', ++ 'Int32', ++ 'MPath', ++ 'OList', ++ 'TSpan', ++ 'UList', ++ 'UTF16', ++ 'Uint8', ++ 'WebGL', ++ 'XPath', ++ 'ETC1', ++ 'HTML', ++ 'Int8', ++ 'S3TC', ++ 'SPv2', ++ 'UTF8', ++ 'API', ++ 'CSS', ++ 'DOM', ++ 'EXT', ++ 'RTC', ++ 'SVG', ++ '2D', ++ 'AX', ++ 'V0', ++ 'V8', ++] ++ ++MATCHING_EXPRESSION = '((?:[A-Z][a-z]+)|[0-9]D?$)' ++ ++ ++class SmartTokenizer(object): ++ """Detects special cases that are not easily discernible without additional ++ knowledge, such as recognizing that in SVGSVGElement, the first two SVGs ++ are separate tokens, but WebGL is one token.""" ++ ++ def __init__(self, name): ++ self.remaining = name ++ ++ def tokenize(self): ++ name = self.remaining ++ tokens = [] ++ while len(name) > 0: ++ matched_token = None ++ for token in SPECIAL_TOKENS: ++ if name.startswith(token): ++ matched_token = token ++ break ++ if not matched_token: ++ match = re.search(MATCHING_EXPRESSION, name) ++ if not match: ++ matched_token = name ++ elif match.start(0) != 0: ++ matched_token = name[:match.start(0)] ++ else: ++ matched_token = match.group(0) ++ tokens.append(name[:len(matched_token)]) ++ name = name[len(matched_token):] ++ return tokens ++ ++ ++class NameStyleConverter(object): ++ """Converts names from camelCase to various other styles. ++ """ ++ ++ def __init__(self, name): ++ self.tokens = self.tokenize(name) ++ ++ def tokenize(self, name): ++ tokenizer = SmartTokenizer(name) ++ return tokenizer.tokenize() ++ ++ def to_snake_case(self): ++ """Snake case is the file and variable name style per Google C++ Style ++ Guide: ++ https://google.github.io/styleguide/cppguide.html#Variable_Names ++ ++ Also known as the hacker case. ++ https://en.wikipedia.org/wiki/Snake_case ++ """ ++ return '_'.join([token.lower() for token in self.tokens]) ++ ++ def to_upper_camel_case(self): ++ """Upper-camel case is the class and function name style per ++ Google C++ Style Guide: ++ https://google.github.io/styleguide/cppguide.html#Function_Names ++ ++ Also known as the PascalCase. ++ https://en.wikipedia.org/wiki/Camel_case. ++ """ ++ return ''.join([token[0].upper() + token[1:] for token in self.tokens]) ++ ++ def to_macro_case(self): ++ """Macro case is the macro name style per Google C++ Style Guide: ++ https://google.github.io/styleguide/cppguide.html#Macro_Names ++ """ ++ return '_'.join([token.upper() for token in self.tokens]) ++ ++ def to_all_cases(self): ++ return { ++ 'snake_case': self.to_snake_case(), ++ 'upper_camel_case': self.to_upper_camel_case(), ++ 'macro_case': self.to_macro_case(), ++ } +--- /dev/null ++++ b/third_party/blink/tools/blinkpy/common/name_style_converter_test.py +@@ -0,0 +1,178 @@ ++# Copyright 2017 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. ++ ++# pylint: disable=import-error,print-statement,relative-import,protected-access ++ ++"""Unit tests for name_style_converter.py.""" ++ ++import unittest ++ ++from name_style_converter import NameStyleConverter ++from name_style_converter import SmartTokenizer ++ ++ ++class SmartTokenizerTest(unittest.TestCase): ++ def test_simple_cases(self): ++ tokenizer = SmartTokenizer('foo') ++ self.assertEqual(tokenizer.tokenize(), ['foo']) ++ ++ tokenizer = SmartTokenizer('fooBar') ++ self.assertEqual(tokenizer.tokenize(), ['foo', 'Bar']) ++ ++ tokenizer = SmartTokenizer('fooBarBaz') ++ self.assertEqual(tokenizer.tokenize(), ['foo', 'Bar', 'Baz']) ++ ++ tokenizer = SmartTokenizer('Baz') ++ self.assertEqual(tokenizer.tokenize(), ['Baz']) ++ ++ tokenizer = SmartTokenizer('') ++ self.assertEqual(tokenizer.tokenize(), []) ++ ++ tokenizer = SmartTokenizer('FOO') ++ self.assertEqual(tokenizer.tokenize(), ['FOO']) ++ ++ tokenizer = SmartTokenizer('foo2') ++ self.assertEqual(tokenizer.tokenize(), ['foo', '2']) ++ ++ def test_tricky_cases(self): ++ tokenizer = SmartTokenizer('XMLHttpRequest') ++ self.assertEqual(tokenizer.tokenize(), ['XML', 'Http', 'Request']) ++ ++ tokenizer = SmartTokenizer('HTMLElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'Element']) ++ ++ tokenizer = SmartTokenizer('WebGLRenderingContext') ++ self.assertEqual(tokenizer.tokenize(), ++ ['WebGL', 'Rendering', 'Context']) ++ ++ tokenizer = SmartTokenizer('CanvasRenderingContext2D') ++ self.assertEqual(tokenizer.tokenize(), ++ ['Canvas', 'Rendering', 'Context', '2D']) ++ tokenizer = SmartTokenizer('CanvasRenderingContext2DAPITest') ++ self.assertEqual(tokenizer.tokenize(), ++ ['Canvas', 'Rendering', 'Context', '2D', 'API', 'Test']) ++ ++ tokenizer = SmartTokenizer('SVGSVGElement') ++ self.assertEqual(tokenizer.tokenize(), ['SVG', 'SVG', 'Element']) ++ ++ tokenizer = SmartTokenizer('CanvasRenderingContext2D') ++ self.assertEqual(tokenizer.tokenize(), ['Canvas', 'Rendering', 'Context', '2D']) ++ ++ tokenizer = SmartTokenizer('CSSURLImageValue') ++ self.assertEqual(tokenizer.tokenize(), ['CSS', 'URL', 'Image', 'Value']) ++ tokenizer = SmartTokenizer('CSSPropertyAPID') ++ self.assertEqual(tokenizer.tokenize(), ['CSS', 'Property', 'API', 'D']) ++ tokenizer = SmartTokenizer('AXARIAGridCell') ++ self.assertEqual(tokenizer.tokenize(), ['AX', 'ARIA', 'Grid', 'Cell']) ++ ++ tokenizer = SmartTokenizer('CDATASection') ++ self.assertEqual(tokenizer.tokenize(), ['CDATA', 'Section']) ++ ++ tokenizer = SmartTokenizer('ASCIICType') ++ self.assertEqual(tokenizer.tokenize(), ['ASCII', 'CType']) ++ tokenizer = SmartTokenizer('CString') ++ self.assertEqual(tokenizer.tokenize(), ['CString']) ++ ++ tokenizer = SmartTokenizer('HTMLDListElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'DList', 'Element']) ++ tokenizer = SmartTokenizer('HTMLOListElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'OList', 'Element']) ++ tokenizer = SmartTokenizer('HTMLIFrameElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'IFrame', 'Element']) ++ tokenizer = SmartTokenizer('HTMLPlugInElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'PlugIn', 'Element']) ++ ++ # No special handling for OptGroup, FieldSet, and TextArea. ++ tokenizer = SmartTokenizer('HTMLOptGroupElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'Opt', 'Group', 'Element']) ++ tokenizer = SmartTokenizer('HTMLFieldSetElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'Field', 'Set', 'Element']) ++ tokenizer = SmartTokenizer('HTMLTextAreaElement') ++ self.assertEqual(tokenizer.tokenize(), ['HTML', 'Text', 'Area', 'Element']) ++ ++ tokenizer = SmartTokenizer('Path2D') ++ self.assertEqual(tokenizer.tokenize(), ['Path', '2D']) ++ tokenizer = SmartTokenizer('Point2D') ++ self.assertEqual(tokenizer.tokenize(), ['Point', '2D']) ++ tokenizer = SmartTokenizer('CanvasRenderingContext2DState') ++ self.assertEqual(tokenizer.tokenize(), ['Canvas', 'Rendering', 'Context', '2D', 'State']) ++ ++ tokenizer = SmartTokenizer('RTCDTMFSender') ++ self.assertEqual(tokenizer.tokenize(), ['RTC', 'DTMF', 'Sender']) ++ ++ tokenizer = SmartTokenizer('WebGLCompressedTextureS3TCsRGB') ++ self.assertEqual(tokenizer.tokenize(), ['WebGL', 'Compressed', 'Texture', 'S3TC', 'sRGB']) ++ tokenizer = SmartTokenizer('WebGL2CompressedTextureETC1') ++ self.assertEqual(tokenizer.tokenize(), ['WebGL2', 'Compressed', 'Texture', 'ETC1']) ++ tokenizer = SmartTokenizer('EXTsRGB') ++ self.assertEqual(tokenizer.tokenize(), ['EXT', 'sRGB']) ++ ++ tokenizer = SmartTokenizer('SVGFEBlendElement') ++ self.assertEqual(tokenizer.tokenize(), ['SVG', 'FE', 'Blend', 'Element']) ++ tokenizer = SmartTokenizer('SVGMPathElement') ++ self.assertEqual(tokenizer.tokenize(), ['SVG', 'MPath', 'Element']) ++ tokenizer = SmartTokenizer('SVGTSpanElement') ++ self.assertEqual(tokenizer.tokenize(), ['SVG', 'TSpan', 'Element']) ++ tokenizer = SmartTokenizer('SVGURIReference') ++ self.assertEqual(tokenizer.tokenize(), ['SVG', 'URI', 'Reference']) ++ ++ tokenizer = SmartTokenizer('UTF16TextIterator') ++ self.assertEqual(tokenizer.tokenize(), ['UTF16', 'Text', 'Iterator']) ++ tokenizer = SmartTokenizer('UTF8Decoder') ++ self.assertEqual(tokenizer.tokenize(), ['UTF8', 'Decoder']) ++ tokenizer = SmartTokenizer('Uint8Array') ++ self.assertEqual(tokenizer.tokenize(), ['Uint8', 'Array']) ++ tokenizer = SmartTokenizer('DOMWindowBase64') ++ self.assertEqual(tokenizer.tokenize(), ['DOM', 'Window', 'Base64']) ++ tokenizer = SmartTokenizer('TextCodecLatin1') ++ self.assertEqual(tokenizer.tokenize(), ['Text', 'Codec', 'Latin1']) ++ tokenizer = SmartTokenizer('V8BindingForCore') ++ self.assertEqual(tokenizer.tokenize(), ['V8', 'Binding', 'For', 'Core']) ++ tokenizer = SmartTokenizer('V8DOMRect') ++ self.assertEqual(tokenizer.tokenize(), ['V8', 'DOM', 'Rect']) ++ ++ tokenizer = SmartTokenizer('V0InsertionPoint') ++ self.assertEqual(tokenizer.tokenize(), ['V0', 'Insertion', 'Point']) ++ tokenizer = SmartTokenizer('ShadowDOMV0Test') ++ self.assertEqual(tokenizer.tokenize(), ['Shadow', 'DOM', 'V0', 'Test']) ++ tokenizer = SmartTokenizer('ElementShadowV0') ++ self.assertEqual(tokenizer.tokenize(), ['Element', 'Shadow', 'V0']) ++ tokenizer = SmartTokenizer('StubChromeClientForSPv2') ++ self.assertEqual(tokenizer.tokenize(), ['Stub', 'Chrome', 'Client', 'For', 'SPv2']) ++ ++ tokenizer = SmartTokenizer('SQLiteAuthorizer') ++ self.assertEqual(tokenizer.tokenize(), ['SQLite', 'Authorizer']) ++ tokenizer = SmartTokenizer('XPathEvaluator') ++ self.assertEqual(tokenizer.tokenize(), ['XPath', 'Evaluator']) ++ ++ tokenizer = SmartTokenizer('IsXHTMLDocument') ++ self.assertEqual(tokenizer.tokenize(), ['Is', 'XHTML', 'Document']) ++ ++ tokenizer = SmartTokenizer('Animation.idl') ++ self.assertEqual(tokenizer.tokenize(), ['Animation', '.idl']) ++ ++ ++class NameStyleConverterTest(unittest.TestCase): ++ def test_snake_case(self): ++ converter = NameStyleConverter('HTMLElement') ++ self.assertEqual(converter.to_snake_case(), 'html_element') ++ ++ def test_upper_camel_case(self): ++ converter = NameStyleConverter('someSuperThing') ++ self.assertEqual(converter.to_upper_camel_case(), 'SomeSuperThing') ++ ++ converter = NameStyleConverter('SVGElement') ++ self.assertEqual(converter.to_upper_camel_case(), 'SVGElement') ++ ++ def test_macro_case(self): ++ converter = NameStyleConverter('WebGLBaz2D') ++ self.assertEqual(converter.to_macro_case(), 'WEBGL_BAZ_2D') ++ ++ def test_all_cases(self): ++ converter = NameStyleConverter('SVGScriptElement') ++ self.assertEqual(converter.to_all_cases(), { ++ 'snake_case': 'svg_script_element', ++ 'upper_camel_case': 'SVGScriptElement', ++ 'macro_case': 'SVG_SCRIPT_ELEMENT', ++ }) +--- /dev/null ++++ b/third_party/blink/tools/compile_devtools_frontend.py +@@ -0,0 +1,20 @@ ++#!/usr/bin/env vpython ++# Copyright 2017 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. ++ ++"""Compile DevTools frontend code with Closure compiler. ++ ++This script wraps devtools/scripts/compile_frontend.py. ++DevTools bot kicks this script. ++""" ++ ++import os ++import sys ++ ++sys.path.append(os.path.join( ++ os.path.dirname(__file__), '..', '..', 'WebKit', 'Source', 'devtools', 'scripts')) ++import compile_frontend ++ ++if __name__ == '__main__': ++ sys.exit(compile_frontend.main()) +--- /dev/null ++++ b/third_party/blink/tools/move_blink_source.py +@@ -0,0 +1,615 @@ ++#!/usr/bin/env vpython ++# Copyright 2017 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. ++ ++"""Tool to move Blink source from third_party/WebKit to third_party/blink. ++ ++See https://docs.google.com/document/d/1l3aPv1Wx__SpRkdOhvJz8ciEGigNT3wFKv78XiuW0Tw/edit?usp=sharing#heading=h.o225wrxp242h ++for the details. ++""" ++ ++import argparse ++import logging ++import os ++import re ++import sys ++from functools import partial ++ ++# Without abspath(), PathFinder can't find chromium_base correctly. ++sys.path.append(os.path.abspath( ++ os.path.join(os.path.dirname(__file__), '..', '..', '..', ++ 'third_party', 'WebKit', 'Tools', 'Scripts'))) ++from blinkpy.common.name_style_converter import NameStyleConverter ++from plan_blink_move import plan_blink_move ++from plan_blink_move import relative_dest ++from webkitpy.common.checkout.git import Git ++from webkitpy.common.path_finder import get_chromium_src_dir ++from webkitpy.common.path_finder import get_scripts_dir ++from webkitpy.common.system.executive import Executive ++from webkitpy.common.system.executive import ScriptError ++from webkitpy.common.system.filesystem import FileSystem ++ ++_log = logging.getLogger('move_blink_source') ++ ++ ++class FileType(object): ++ NONE = 0 ++ BUILD = 1 ++ BLINK_BUILD = 2 ++ OWNERS = 3 ++ DEPS = 4 ++ MOJOM = 5 ++ TYPEMAP = 6 ++ BLINK_BUILD_PY = 7 ++ LAYOUT_TESTS_WITH_MOJOM = 8 ++ ++ @staticmethod ++ def detect(path): ++ slash_dir, basename = os.path.split(path) ++ slash_dir = slash_dir.replace(os.path.sep, '/') ++ if basename == 'DEPS': ++ return FileType.DEPS ++ if basename == 'OWNERS': ++ return FileType.OWNERS ++ if basename.endswith('.mojom'): ++ return FileType.MOJOM ++ if basename.endswith('.typemap'): ++ return FileType.TYPEMAP ++ if basename.endswith('.py') and 'third_party/WebKit/Source/build' in slash_dir: ++ return FileType.BLINK_BUILD_PY ++ if basename.endswith(('.gn', '.gni')): ++ if 'third_party/WebKit' in path or 'third_party/blink' in slash_dir: ++ return FileType.BLINK_BUILD ++ if 'third_party' in slash_dir: ++ return FileType.NONE ++ return FileType.BUILD ++ if basename.endswith('.html') and re.search( ++ r'third_party/WebKit/LayoutTests/(geolocation-api|installedapp|' + ++ r'media/mediasession|payments|presentation|webshare)', slash_dir): ++ return FileType.LAYOUT_TESTS_WITH_MOJOM ++ return FileType.NONE ++ ++ ++class MoveBlinkSource(object): ++ ++ def __init__(self, fs, options, repo_root): ++ self._fs = fs ++ self._options = options ++ _log.debug(options) ++ self._repo_root = repo_root ++ ++ # The following fields are initialized in _create_basename_maps. ++ self._basename_map = None ++ self._basename_re = None ++ self._idl_generated_impl_headers = None ++ # _checked_in_header_re is used to distinguish checked-in header files ++ # and generated header files. ++ self._checked_in_header_re = None ++ ++ self._updated_files = [] ++ ++ def update(self, apply_only=None): ++ """Updates contents of files affected by Blink source move. ++ ++ Args: ++ apply_only: If it's None, updates all affected files. Otherwise, ++ it should be a set of file paths and this function updates ++ only the files in |apply_only|. ++ """ ++ _log.info('Planning renaming ...') ++ file_pairs = plan_blink_move(self._fs, []) ++ _log.info('Will move %d files', len(file_pairs)) ++ ++ self._create_basename_maps(file_pairs) ++ dirs = self._update_file_content(apply_only) ++ ++ # Updates #includes in files in directories with updated DEPS + ++ # third_party/WebKit/{Source,common,public}. ++ self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'third_party', 'WebKit', 'Source')) ++ self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'third_party', 'WebKit', 'common')) ++ self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'third_party', 'WebKit', 'public')) ++ self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'mojo', 'public', 'tools', ++ 'bindings', 'generators', 'cpp_templates')) ++ self._update_cpp_includes_in_directories(dirs, apply_only) ++ ++ # Content update for individual files. ++ # The following is a list of tuples. ++ # Tuple: (<file path relative to repo root>, [replacement commands]) ++ # Command: a callable object, or ++ # a tuple of (<original string>, <new string>). ++ file_replacement_list = [ ++ ('DEPS', ++ [('src/third_party/WebKit/Source/devtools', ++ 'src/third_party/blink/renderer/devtools')]), ++ ('WATCHLISTS', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer'), ++ ('third_party/WebKit/public', 'third_party/blink/renderer/public')]), ++ ('build/check_gn_headers_whitelist.txt', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer'), ++ ('third_party/WebKit/public', 'third_party/blink/renderer/public'), ++ self._update_basename]), ++ ('testing/buildbot/gn_isolate_map.pyl', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('third_party/WebKit/Source/BUILD.gn', ++ [('$root_gen_dir/third_party/WebKit', ++ '$root_gen_dir/third_party/blink/renderer')]), ++ ('third_party/WebKit/Source/config.gni', ++ [('snake_case_source_files = false', ++ 'snake_case_source_files = true')]), ++ ('third_party/WebKit/Source/core/css/CSSProperties.json5', ++ [self._update_basename]), ++ ('third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5', ++ [self._update_basename]), ++ ('third_party/WebKit/Source/core/css/ComputedStyleFieldAliases.json5', ++ [self._update_basename]), ++ ('third_party/WebKit/Source/core/html/parser/create-html-entity-table', ++ [self._update_basename]), ++ ('third_party/WebKit/Source/core/inspector/inspector_protocol_config.json', ++ [self._update_basename]), ++ ('third_party/WebKit/Source/core/probe/CoreProbes.json5', ++ [self._update_basename]), ++ ('third_party/WebKit/Source/core/testing/InternalSettings.h', ++ [('InternalSettingsGenerated.h', 'internal_settings_generated.h')]), ++ ('third_party/WebKit/Source/core/testing/Internals.cpp', ++ [('InternalRuntimeFlags.h', 'internal_runtime_flags.h')]), ++ ('third_party/WebKit/Source/platform/probe/PlatformProbes.json5', ++ [self._update_basename]), ++ ('third_party/WebKit/public/BUILD.gn', ++ [('$root_gen_dir/third_party/WebKit', ++ '$root_gen_dir/third_party/blink/renderer')]), ++ ('third_party/WebKit/public/blink_resources.grd', ++ [('../Source/', '../')]), ++ ('tools/android/eclipse/.classpath', ++ [('third_party/WebKit/public', 'third_party/blink/renderer/public')]), ++ ('tools/android/loading/cloud/backend/deploy.sh', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('tools/android/loading/emulation_unittest.py', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('tools/android/loading/options.py', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('tools/android/loading/request_track.py', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('tools/gritsettings/resource_ids', ++ [('third_party/WebKit/public', 'third_party/blink/renderer/public'), ++ ('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('tools/metrics/actions/extract_actions.py', ++ [('third_party/WebKit/Source', 'third_party/blink/renderer')]), ++ ('tools/metrics/histograms/update_editor_commands.py', ++ [('third_party/WebKit/Source/core/editing/EditorCommand.cpp', ++ 'third_party/blink/renderer/core/editing/editor_command.cc')]), ++ ('tools/metrics/histograms/update_use_counter_css.py', ++ [('third_party/WebKit/Source/core/frame/UseCounter.cpp', ++ 'third_party/blink/renderer/core/frame/use_counter.cc')]), ++ ('tools/metrics/histograms/update_use_counter_feature_enum.py', ++ [('third_party/WebKit/public', 'third_party/blink/renderer/public')]), ++ ] ++ for file_path, replacement_list in file_replacement_list: ++ if not apply_only or file_path in apply_only: ++ self._update_single_file_content(file_path, replacement_list, should_write=self._options.run) ++ ++ if self._options.run: ++ _log.info('Formatting updated %d files ...', len(self._updated_files)) ++ git = Git(cwd=self._repo_root) ++ # |git cl format| can't handle too many files at once. ++ while len(self._updated_files) > 0: ++ end_index = 100 ++ if end_index > len(self._updated_files): ++ end_index = len(self._updated_files) ++ git.run(['cl', 'format'] + self._updated_files[:end_index]) ++ self._updated_files = self._updated_files[end_index:] ++ ++ if not apply_only: ++ _log.info('Make a local commit ...') ++ git.commit_locally_with_message("""The Great Blink mv for source files, part 1. ++ ++Update file contents without moving files. ++ ++NOAUTOREVERT=true ++Bug: 768828 ++""") ++ ++ def move(self, apply_only=None): ++ """Move Blink source files. ++ ++ Args: ++ apply_only: If it's None, move all affected files. Otherwise, ++ it should be a set of file paths and this function moves ++ only the files in |apply_only|. ++ """ ++ _log.info('Planning renaming ...') ++ file_pairs = plan_blink_move(self._fs, []) ++ ++ if apply_only: ++ file_pairs = [(src, dest) for (src, dest) in file_pairs ++ if 'third_party/WebKit/' + src.replace('\\', '/') in apply_only] ++ print 'Update file_pairs = ', file_pairs ++ _log.info('Will move %d files', len(file_pairs)) ++ ++ git = Git(cwd=self._repo_root) ++ files_set = self._get_checked_in_files(git) ++ for i, (src, dest) in enumerate(file_pairs): ++ src_from_repo = self._fs.join('third_party', 'WebKit', src) ++ if src_from_repo.replace('\\', '/') not in files_set: ++ _log.info('%s is not in the repository', src) ++ continue ++ dest_from_repo = self._fs.join('third_party', 'blink', dest) ++ self._fs.maybe_make_directory(self._repo_root, 'third_party', 'blink', self._fs.dirname(dest)) ++ if self._options.run_git: ++ git.move(src_from_repo, dest_from_repo) ++ _log.info('[%d/%d] Git moved %s', i + 1, len(file_pairs), src) ++ else: ++ self._fs.move(self._fs.join(self._repo_root, src_from_repo), ++ self._fs.join(self._repo_root, dest_from_repo)) ++ _log.info('[%d/%d] Moved %s', i + 1, len(file_pairs), src) ++ if apply_only: ++ return ++ ++ self._update_single_file_content( ++ 'build/get_landmines.py', ++ [('\ndef main', ' print \'The Great Blink mv for source files (crbug.com/768828)\'\n\ndef main')]) ++ ++ _log.info('Run run-bindings-tests ...') ++ Executive().run_command(['python', ++ self._fs.join(get_scripts_dir(), 'run-bindings-tests'), ++ '--reset-results'], ++ cwd=self._repo_root) ++ ++ if self._options.run_git: ++ _log.info('Make a local commit ...') ++ git.commit_locally_with_message("""The Great Blink mv for source files, part 2. ++ ++Move and rename files. ++ ++NOAUTOREVERT=true ++Bug: 768828 ++""") ++ ++ def fix_branch(self): ++ git = Git(cwd=self._repo_root) ++ status = self._get_local_change_status(git) ++ if len(status) == 0: ++ _log.info('No local changes.') ++ return ++ modified_files = {f for (s, f) in status if s != 'D'} ++ deleted_files = {f for (s, f) in status if s == 'D'} ++ ++ self.update(apply_only=modified_files) ++ self.move(apply_only=modified_files) ++ try: ++ git.commit_locally_with_message('This commit should be squashed.') ++ except ScriptError: ++ _log.info('move_blink_source.py modified nothing.') ++ ++ # TODO(tkent): Show a message about deleted_files. ++ ++ def _get_local_change_status(self, git): ++ """Returns a list of tuples representing local change summary. ++ ++ Each tuple contains two strings. The first one is file change status ++ such as "M", "D". See --diff-filter section of git-diff manual page. ++ The second one is file name relative to the repository top. ++ """ ++ ++ base_commit = git.run(['show-branch', '--merge-base', 'master', 'HEAD']).strip() ++ # Note that file names in the following command result are always ++ # slash-separated, even on Windows. ++ status_lines = git.run(['diff', '--name-status', '--no-renames', base_commit]).split('\n') ++ status_tuple_list = [] ++ for l in status_lines: ++ items = l.split('\t') ++ if len(items) == 2: ++ status_tuple_list.append(tuple(items)) ++ elif len(l) > 0: ++ _log.warning('Unrecognized diff output: "%s"', l) ++ return status_tuple_list ++ ++ def _get_checked_in_files(self, git): ++ files_text = git.run(['ls-files', ++ 'third_party/WebKit/Source', ++ 'third_party/WebKit/common', ++ 'third_party/WebKit/public']) ++ return set(files_text.split('\n')) ++ ++ def _create_basename_maps(self, file_pairs): ++ basename_map = {} ++ # Generated inspector/protocol/* contains a lot of names duplicated with ++ # checked-in core files. We don't want to rename them, and don't want to ++ # replace them in BUILD.gn and #include accidentally. ++ pattern = r'(?<!inspector/protocol/)\b(' ++ idl_headers = set() ++ header_pattern = r'(?<!inspector/protocol/)\b(' ++ for source, dest in file_pairs: ++ _, source_base = self._fs.split(source) ++ _, dest_base = self._fs.split(dest) ++ # OriginTrialFeaturesForCore.h in bindings/tests/results/modules/ ++ # confuses generated/checked-in detection in _replace_include_path(). ++ if 'bindings/tests' in source.replace('\\', '/'): ++ continue ++ if source_base.endswith('.h'): ++ header_pattern += re.escape(source_base) + '|' ++ if source_base == dest_base: ++ continue ++ basename_map[source_base] = dest_base ++ pattern += re.escape(source_base) + '|' ++ # IDL sometimes generates implementation files as well as ++ # binding files. We'd like to update #includes for such files. ++ if source_base.endswith('.idl'): ++ source_header = source_base.replace('.idl', '.h') ++ basename_map[source_header] = dest_base.replace('.idl', '.h') ++ pattern += re.escape(source_header) + '|' ++ idl_headers.add(source_header) ++ _log.info('Rename %d files for snake_case', len(basename_map)) ++ self._basename_map = basename_map ++ self._basename_re = re.compile(pattern[0:len(pattern) - 1] + ')(?=["\']|$)') ++ self._idl_generated_impl_headers = idl_headers ++ self._checked_in_header_re = re.compile(header_pattern[0:len(header_pattern) - 1] + ')$') ++ ++ def _shorten_path(self, path): ++ if path.startswith(self._repo_root): ++ return path[len(self._repo_root) + 1:] ++ return path ++ ++ @staticmethod ++ def _filter_file(fs, dirname, basename): ++ return FileType.detect(fs.join(dirname, basename)) != FileType.NONE ++ ++ def _update_build(self, content): ++ content = content.replace('//third_party/WebKit/Source', '//third_party/blink/renderer') ++ content = content.replace('//third_party/WebKit/common', '//third_party/blink/common') ++ content = content.replace('//third_party/WebKit/public', '//third_party/blink/renderer/public') ++ # export_header_blink exists outside of Blink too. ++ content = content.replace('export_header_blink = "third_party/WebKit/public/platform/WebCommon.h"', ++ 'export_header_blink = "third_party/blink/renderer/public/platform/web_common.h"') ++ return content ++ ++ def _update_blink_build(self, content): ++ content = self._update_build(content) ++ ++ # Update visibility=[...] ++ content = content.replace('//third_party/WebKit/*', '//third_party/blink/*') ++ content = content.replace('//third_party/WebKit/Source/*', '//third_party/blink/renderer/*') ++ content = content.replace('//third_party/WebKit/public/*', '//third_party/blink/renderer/public/*') ++ ++ # Update mojom variables ++ content = content.replace('export_header = "third_party/WebKit/common', ++ 'export_header = "third_party/blink/common') ++ content = content.replace('export_header_blink = "third_party/WebKit/Source', ++ 'export_header_blink = "third_party/blink/renderer') ++ return self._update_basename(content) ++ ++ def _update_owners(self, content): ++ content = content.replace('//third_party/WebKit/Source', '//third_party/blink/renderer') ++ content = content.replace('//third_party/WebKit/common', '//third_party/blink/common') ++ content = content.replace('//third_party/WebKit/public', '//third_party/blink/renderer/public') ++ return content ++ ++ def _update_deps(self, content): ++ original_content = content ++ content = content.replace('third_party/WebKit/Source', 'third_party/blink/renderer') ++ content = content.replace('third_party/WebKit/common', 'third_party/blink/common') ++ content = content.replace('third_party/WebKit/public', 'third_party/blink/renderer/public') ++ content = content.replace('third_party/WebKit', 'third_party/blink') ++ if original_content == content: ++ return content ++ return self._update_basename(content) ++ ++ def _update_mojom(self, content): ++ content = content.replace('third_party/WebKit/public', 'third_party/blink/renderer/public') ++ content = content.replace('third_party/WebKit/common', 'third_party/blink/common') ++ return content ++ ++ def _update_typemap(self, content): ++ content = content.replace('//third_party/WebKit/Source', '//third_party/blink/renderer') ++ content = content.replace('//third_party/WebKit/common', '//third_party/blink/common') ++ content = content.replace('//third_party/WebKit/public', '//third_party/blink/renderer/public') ++ return self._update_basename(content) ++ ++ def _update_blink_build_py(self, content): ++ # We don't prepend 'third_party/blink/renderer/' to matched basenames ++ # because it won't affect build and manual update after the great mv is ++ # enough. ++ return self._update_basename(content) ++ ++ def _update_layout_tests(self, content): ++ return content.replace('file:///gen/third_party/WebKit/', 'file:///gen/third_party/blink/renderer/') ++ ++ def _update_basename(self, content): ++ return self._basename_re.sub(lambda match: self._basename_map[match.group(1)], content) ++ ++ @staticmethod ++ def _append_unless_upper_dir_exists(dirs, new_dir): ++ for i in range(0, len(dirs)): ++ if new_dir.startswith(dirs[i]): ++ return ++ if dirs[i].startswith(new_dir): ++ dirs[i] = new_dir ++ return ++ dirs.append(new_dir) ++ ++ def _update_file_content(self, apply_only): ++ _log.info('Find *.gn, *.mojom, *.py, *.typemap, DEPS, and OWNERS ...') ++ files = self._fs.files_under( ++ self._repo_root, dirs_to_skip=['.git', 'out'], file_filter=self._filter_file) ++ _log.info('Scan contents of %d files ...', len(files)) ++ updated_deps_dirs = [] ++ for file_path in files: ++ file_type = FileType.detect(file_path) ++ original_content = self._fs.read_text_file(file_path) ++ content = original_content ++ if file_type == FileType.BUILD: ++ content = self._update_build(content) ++ elif file_type == FileType.BLINK_BUILD: ++ content = self._update_blink_build(content) ++ elif file_type == FileType.OWNERS: ++ content = self._update_owners(content) ++ elif file_type == FileType.DEPS: ++ if self._fs.dirname(file_path) == self._repo_root: ++ _log.info("Skip //DEPS") ++ continue ++ content = self._update_deps(content) ++ elif file_type == FileType.MOJOM: ++ content = self._update_mojom(content) ++ elif file_type == FileType.TYPEMAP: ++ content = self._update_typemap(content) ++ elif file_type == FileType.BLINK_BUILD_PY: ++ content = self._update_blink_build_py(content) ++ elif file_type == FileType.LAYOUT_TESTS_WITH_MOJOM: ++ content = self._update_layout_tests(content) ++ ++ if original_content == content: ++ continue ++ if self._options.run and (not apply_only or file_path.replace('\\', '/') in apply_only): ++ self._fs.write_text_file(file_path, content) ++ self._updated_files.append(file_path) ++ if file_type == FileType.DEPS: ++ self._append_unless_upper_dir_exists(updated_deps_dirs, self._fs.dirname(file_path)) ++ _log.info('Updated %s', self._shorten_path(file_path)) ++ return updated_deps_dirs ++ ++ def _update_cpp_includes_in_directories(self, dirs, apply_only): ++ for dirname in dirs: ++ _log.info('Processing #include in %s ...', self._shorten_path(dirname)) ++ files = self._fs.files_under( ++ dirname, file_filter=lambda fs, _, basename: basename.endswith( ++ ('.h', '.cc', '.cpp', '.mm', '.cc.tmpl', '.cpp.tmpl', ++ '.h.tmpl', 'XPathGrammar.y', '.gperf'))) ++ for file_path in files: ++ posix_file_path = file_path.replace('\\', '/') ++ original_content = self._fs.read_text_file(file_path) ++ ++ content = self._update_cpp_includes(original_content) ++ if file_path.endswith('.h') and '/third_party/WebKit/public/' in posix_file_path: ++ content = self._update_basename_only_includes(content, file_path) ++ if file_path.endswith('.h') and '/third_party/WebKit/' in posix_file_path: ++ content = self._update_include_guard(content, file_path) ++ ++ if original_content == content: ++ continue ++ if self._options.run and (not apply_only or posix_file_path in apply_only): ++ self._fs.write_text_file(file_path, content) ++ self._updated_files.append(file_path) ++ _log.info('Updated %s', self._shorten_path(file_path)) ++ ++ def _replace_include_path(self, match): ++ include_or_import = match.group(1) ++ path = match.group(2) ++ ++ # If |path| starts with 'third_party/WebKit', we should adjust the ++ # directory name for third_party/blink, and replace its basename by ++ # self._basename_map. ++ # ++ # If |path| starts with a Blink-internal directory such as bindings, ++ # core, modules, platform, public, it refers to a checked-in file, or a ++ # generated file. For the former, we should add ++ # 'third_party/blink/renderer/' and replace the basename. For the ++ # latter, we should update the basename for a name mapped from an IDL ++ # renaming, and should not add 'third_party/blink/renderer'. ++ ++ if path.startswith('third_party/WebKit'): ++ path = path.replace('third_party/WebKit/Source', 'third_party/blink/renderer') ++ path = path.replace('third_party/WebKit/common', 'third_party/blink/common') ++ path = path.replace('third_party/WebKit/public', 'third_party/blink/renderer/public') ++ path = self._update_basename(path) ++ return '#%s "%s"' % (include_or_import, path) ++ ++ match = self._checked_in_header_re.search(path) ++ if match: ++ if match.group(1) in self._basename_map: ++ path = 'third_party/blink/renderer/' + path[:match.start(1)] + self._basename_map[match.group(1)] ++ else: ++ path = 'third_party/blink/renderer/' + path ++ elif 'core/inspector/protocol/' not in path: ++ basename_start = path.rfind('/') + 1 ++ basename = path[basename_start:] ++ if basename in self._idl_generated_impl_headers: ++ path = path[:basename_start] + self._basename_map[basename] ++ elif basename.startswith('V8'): ++ path = path[:basename_start] + NameStyleConverter(basename[:len(basename) - 2]).to_snake_case() + '.h' ++ return '#%s "%s"' % (include_or_import, path) ++ ++ def _update_cpp_includes(self, content): ++ pattern = re.compile(r'#(include|import)\s+"((bindings|core|modules|platform|public|' + ++ r'third_party/WebKit/(Source|common|public))/[-_\w/.]+)"') ++ return pattern.sub(self._replace_include_path, content) ++ ++ def _replace_basename_only_include(self, subdir, source_path, match): ++ source_basename = match.group(1) ++ if source_basename in self._basename_map: ++ return '#include "third_party/blink/renderer/public/%s/%s"' % (subdir, self._basename_map[source_basename]) ++ _log.warning('Basename-only %s in %s', match.group(0), self._shorten_path(source_path)) ++ return match.group(0) ++ ++ def _update_basename_only_includes(self, content, source_path): ++ if not source_path.endswith('.h') or '/third_party/WebKit/public/' not in source_path.replace('\\', '/'): ++ return ++ # In public/ header files, we should replace |#include "WebFoo.h"| ++ # with |#include "third_party/blink/renderer/public/platform-or-web/web_foo.h"| ++ subdir = self._fs.basename(self._fs.dirname(source_path)) ++ # subdir is 'web' or 'platform'. ++ return re.sub(r'#include\s+"(\w+\.h)"', ++ partial(self._replace_basename_only_include, subdir, source_path), content) ++ ++ def _update_include_guard(self, content, source_path): ++ current_guard = re.sub(r'[.]', '_', self._fs.basename(source_path)) ++ new_path = relative_dest(self._fs, self._fs.relpath( ++ source_path, start=self._fs.join(self._repo_root, 'third_party', 'WebKit'))) ++ new_guard = 'THIRD_PARTY_BLINK_' + re.sub(r'[\\/.]', '_', new_path.upper()) + '_' ++ content = re.sub(r'#ifndef\s+(WTF_)?' + current_guard, '#ifndef ' + new_guard, content); ++ content = re.sub(r'#define\s+(WTF_)?' + current_guard, '#define ' + new_guard, content); ++ content = re.sub(r'#endif\s+//\s+(WTF_)?' + current_guard, '#endif // ' + new_guard, content); ++ return content ++ ++ def _update_single_file_content(self, file_path, replace_list, should_write=True): ++ full_path = self._fs.join(self._repo_root, file_path) ++ original_content = self._fs.read_text_file(full_path) ++ content = original_content ++ for command in replace_list: ++ if isinstance(command, tuple): ++ src, dest = command ++ content = content.replace(src, dest) ++ elif callable(command): ++ content = command(content) ++ else: ++ raise TypeError('A tuple or a function is expected.') ++ if content != original_content: ++ if should_write: ++ self._fs.write_text_file(full_path, content) ++ self._updated_files.append(full_path) ++ _log.info('Updated %s', file_path) ++ else: ++ _log.warning('%s does not contain specified source strings.', file_path) ++ ++ ++def main(): ++ logging.basicConfig(level=logging.DEBUG, ++ format='[%(asctime)s %(levelname)s %(name)s] %(message)s', ++ datefmt='%H:%M:%S') ++ parser = argparse.ArgumentParser(description='Blink source mover') ++ sub_parsers = parser.add_subparsers() ++ ++ update_parser = sub_parsers.add_parser('update') ++ update_parser.set_defaults(command='update') ++ update_parser.add_argument('--run', dest='run', action='store_true', ++ help='Update file contents') ++ ++ move_parser = sub_parsers.add_parser('move') ++ move_parser.set_defaults(command='move') ++ move_parser.add_argument('--git', dest='run_git', action='store_true', ++ help='Run |git mv| command instead of |mv|.') ++ ++ fixbranch_parser = sub_parsers.add_parser('fixbranch') ++ fixbranch_parser.set_defaults(command='fixbranch', run=True, run_git=True) ++ ++ options = parser.parse_args() ++ mover = MoveBlinkSource(FileSystem(), options, get_chromium_src_dir()) ++ if options.command == 'update': ++ mover.update() ++ elif options.command == 'move': ++ mover.move() ++ elif options.command == 'fixbranch': ++ mover.fix_branch() ++ ++ ++if __name__ == '__main__': ++ main() +--- /dev/null ++++ b/third_party/blink/tools/plan_blink_move.py +@@ -0,0 +1,96 @@ ++#!/usr/bin/env vpython ++# Copyright 2017 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. ++ ++import os ++import re ++import sys ++ ++sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', ++ 'third_party', 'WebKit', 'Tools', 'Scripts')) ++from blinkpy.common.name_style_converter import NameStyleConverter ++from webkitpy.common.system.filesystem import FileSystem ++ ++ ++def relative_dest(fs, filename): ++ """Returns a destination path string for given filename. ++ ++ |filename| is a path relative to third_party/WebKit, and the resultant path ++ is relative to third_party/blink. ++ """ ++ dest = None ++ if filename.startswith('public'): ++ dest = re.sub(r'^public', 'renderer' + fs.sep + 'public', filename) ++ elif filename.startswith('Source'): ++ dest = re.sub(r'^Source', 'renderer', filename) ++ elif filename.startswith('common'): ++ dest = filename ++ else: ++ raise ValueError('|filename| must start with "common", "public", or "Source": %s' % filename) ++ if filename.endswith(('.h', '.cpp', '.mm', '.idl', '.typemap', 'Settings.json5')): ++ dirname, basename = fs.split(dest) ++ basename, ext = fs.splitext(basename) ++ # Skip some inspector-related files. #includes for these files are ++ # generated by a script outside of Blink. ++ if (re.match(r'Inspector.*Agent', basename) ++ or basename == 'InspectorTraceEvents' ++ or basename == 'PerformanceMonitor' ++ or basename == 'PlatformTraceEventsAgent'): ++ return dest ++ # Skip CSSProperty*. Some files are generated, and some files are ++ # checked-in. It's hard to handle them automatically. ++ if re.search(r'css[\\/]properties$', dirname): ++ return dest ++ if filename.endswith('.cpp'): ++ ext = '.cc' ++ # WebKit.h should be renamed to blink.h. ++ if basename == 'WebKit' and ext == '.h': ++ basename = 'blink' ++ if basename.lower() != basename: ++ basename = NameStyleConverter(basename).to_snake_case() ++ return fs.join(dirname, basename + ext) ++ return dest ++ ++ ++def start_with_list(name, prefixes): ++ if len(prefixes) == 0: ++ return True ++ for prefix in prefixes: ++ if name.startswith(prefix): ++ return True ++ return False ++ ++ ++def plan_blink_move(fs, prefixes): ++ """Returns (source, dest) path pairs. ++ ++ The source paths are relative to third_party/WebKit, ++ and the dest paths are relative to third_party/blink. ++ The paths use os.sep as the path part separator. ++ """ ++ blink_dir = fs.join(fs.dirname(__file__), '..') ++ webkit_dir = fs.join(blink_dir, '..', '..', 'third_party', 'WebKit') ++ source_files = fs.files_under(fs.join(webkit_dir, 'Source')) ++ source_files += fs.files_under(fs.join(webkit_dir, 'common')) ++ source_files += fs.files_under(fs.join(webkit_dir, 'public')) ++ ++ # It's possible to check git.exists() here, but we don't do it due to slow ++ # performance. We should check it just before executing git command. ++ ++ source_files = [f[len(webkit_dir) + 1:] for f in source_files] ++ return [(f, relative_dest(fs, f)) for f in source_files ++ if f.find('node_modules') == -1 and start_with_list(f, prefixes)] ++ ++ ++def main(): ++ fs = FileSystem() ++ file_pairs = plan_blink_move(fs, sys.argv[1:]) ++ print 'Show renaming plan. It contains files not in the repository.' ++ print '<Source path relative to third_party/WebKit> => <Destination path relative to third_party/blink>' ++ for pair in file_pairs: ++ print '%s\t=>\t%s' % pair ++ ++ ++if __name__ == '__main__': ++ main() |