OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright 2014 The Chromium 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 import argparse |
| 7 import json |
| 8 import os |
| 9 import re |
| 10 import sys |
| 11 |
| 12 _BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)' |
| 13 require_regex = re.compile(_BASE_REGEX_STRING % 'require') |
| 14 provide_regex = re.compile(_BASE_REGEX_STRING % 'provide') |
| 15 |
| 16 |
| 17 def ProcessFile(filename): |
| 18 """Extracts provided and required namespaces. |
| 19 |
| 20 Description: |
| 21 Scans Javascript file for provided and required namespaces. |
| 22 |
| 23 Args: |
| 24 filename: name of the file to process. |
| 25 |
| 26 Returns: |
| 27 Pair of lists, where the first list contains namespaces provided by the file |
| 28 and the second contains a list of requirements. |
| 29 """ |
| 30 |
| 31 provides = [] |
| 32 requires = [] |
| 33 with open(filename, 'r') as file_handle: |
| 34 for line in file_handle: |
| 35 if re.match(require_regex, line): |
| 36 requires.append(re.search(require_regex, line).group(1)) |
| 37 if re.match(provide_regex, line): |
| 38 provides.append(re.search(provide_regex, line).group(1)) |
| 39 return provides, requires |
| 40 |
| 41 |
| 42 def ExtractDependencies(filename, providers, requirements): |
| 43 """Extracts provided and required namespaces for a file. |
| 44 |
| 45 Description: |
| 46 Updates maps for namespace providers and file prerequisites. |
| 47 |
| 48 Args: |
| 49 filename: Path of the file to process. |
| 50 providers: Mapping of namespace to filename that provides the namespace. |
| 51 requirements: Mapping of filename to a list of prerequisite namespaces. |
| 52 """ |
| 53 |
| 54 p, r = ProcessFile(filename) |
| 55 |
| 56 for name in p: |
| 57 providers[name] = filename |
| 58 for name in r: |
| 59 if not filename in requirements: |
| 60 requirements[filename] = [] |
| 61 requirements[filename].append(name) |
| 62 |
| 63 |
| 64 def Export(target_file, source_filename, providers, requirements, processed): |
| 65 """Writes the contents of a file. |
| 66 |
| 67 Description: |
| 68 Appends the contents of the source file to the target file. In order to |
| 69 preserve proper dependencies, each file has its required namespaces |
| 70 processed before exporting the source file itself. The set of exported files |
| 71 is tracked to guard against multiple exports of the same file. Comments as |
| 72 well as 'provide' and 'require' statements are removed during to export to |
| 73 reduce file size. |
| 74 |
| 75 Args: |
| 76 target_file: Handle to target file for export. |
| 77 source_filename: Name of the file to export. |
| 78 providers: Map of namespace to filename. |
| 79 requirements: Map of filename to required namespaces. |
| 80 processed: Set of processed files. |
| 81 Returns: |
| 82 """ |
| 83 |
| 84 # Filename may have already been processed if it was a requirement of a |
| 85 # previous exported file. |
| 86 if source_filename in processed: |
| 87 return |
| 88 |
| 89 # Export requirements before file. |
| 90 if source_filename in requirements: |
| 91 for namespace in requirements[source_filename]: |
| 92 if namespace in providers: |
| 93 dependency = providers[namespace] |
| 94 if dependency: |
| 95 Export(target_file, dependency, providers, requirements, processed) |
| 96 |
| 97 # Export file |
| 98 processed.add(source_filename) |
| 99 for name in providers: |
| 100 if providers[name] == source_filename: |
| 101 target_file.write('// %s%s' % (name, os.linesep)) |
| 102 source_file = open(source_filename, 'r') |
| 103 try: |
| 104 comment_block = False |
| 105 for line in source_file: |
| 106 # Skip require and provide statements. |
| 107 if (not re.match(require_regex, line) and not |
| 108 re.match(provide_regex, line)): |
| 109 formatted = line.rstrip() |
| 110 if comment_block: |
| 111 # Scan for trailing */ in multi-line comment. |
| 112 index = formatted.find('*/') |
| 113 if index >= 0: |
| 114 formatted = formatted[index + 2:] |
| 115 comment_block = False |
| 116 else: |
| 117 formatted = '' |
| 118 # Remove // style comments. |
| 119 index = formatted.find('//') |
| 120 if index >= 0: |
| 121 formatted = formatted[:index] |
| 122 # Remove /* */ style comments. |
| 123 start_comment = formatted.find('/*') |
| 124 end_comment = formatted.find('*/') |
| 125 while start_comment >= 0: |
| 126 if end_comment > start_comment: |
| 127 formatted = (formatted[:start_comment] |
| 128 + formatted[end_comment + 2:]) |
| 129 start_comment = formatted.find('/*') |
| 130 end_comment = formatted.find('*/') |
| 131 else: |
| 132 formatted = formatted[:start_comment] |
| 133 comment_block = True |
| 134 start_comment = -1 |
| 135 if formatted.strip(): |
| 136 target_file.write('%s%s' % (formatted, os.linesep)) |
| 137 finally: |
| 138 source_file.close() |
| 139 target_file.write('\n') |
| 140 |
| 141 |
| 142 def ExtractSources(options): |
| 143 """Extracts list of sources based on command line options. |
| 144 |
| 145 Args: |
| 146 options: Parsed command line options. |
| 147 Returns: |
| 148 List of source files. If the path option is specified then file paths are |
| 149 absolute. Otherwise, relative paths may be used. |
| 150 """ |
| 151 |
| 152 sources = None |
| 153 if options.json_file: |
| 154 # Optionally load list of source files from a json file. Useful when the |
| 155 # list of files to process is too long for the command line. |
| 156 with open(options.json_file, 'r') as json_file: |
| 157 data = [] |
| 158 # Strip leading comments. |
| 159 for line in json_file: |
| 160 if not line.startswith('#'): |
| 161 data.append(line) |
| 162 json_object = json.loads(os.linesep.join(data).replace('\'', '\"')) |
| 163 path = options.json_sources.split('.') |
| 164 sources = json_object |
| 165 for key in path: |
| 166 sources = sources[key] |
| 167 if options.path: |
| 168 sources = [os.path.join(options.path, source) for source in sources] |
| 169 else: |
| 170 sources = options.sources |
| 171 return sources |
| 172 |
| 173 |
| 174 def main(): |
| 175 """The entrypoint for this script.""" |
| 176 parser = argparse.ArgumentParser() |
| 177 parser.add_argument('--sources', nargs='*') |
| 178 parser.add_argument('--target', nargs=1) |
| 179 parser.add_argument('--json_file', nargs='?') |
| 180 parser.add_argument('--json_sources', nargs='?') |
| 181 parser.add_argument('--path', nargs='?') |
| 182 options = parser.parse_args() |
| 183 |
| 184 sources = ExtractSources(options) |
| 185 assert sources, 'Missing source files.' |
| 186 |
| 187 providers = {} |
| 188 requirements = {} |
| 189 for file in sources: |
| 190 ExtractDependencies(file, providers, requirements) |
| 191 |
| 192 with open(options.target[0], 'w') as target_file: |
| 193 processed = set() |
| 194 for source_filename in sources: |
| 195 Export(target_file, source_filename, providers, requirements, processed) |
| 196 |
| 197 if __name__ == '__main__': |
| 198 main() |
OLD | NEW |