| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Process Chrome resources (HTML/CSS/JS) to handle <include> and <if> tags.""" | 5 """Process Chrome resources (HTML/CSS/JS) to handle <include> and <if> tags.""" |
| 6 | 6 |
| 7 from collections import defaultdict | 7 from collections import defaultdict |
| 8 import re | 8 import re |
| 9 import os | 9 import os |
| 10 | 10 |
| 11 | 11 |
| 12 class LineNumber(object): | 12 class LineNumber(object): |
| 13 """A simple wrapper to hold line information (e.g. file.js:32). | 13 """A simple wrapper to hold line information (e.g. file.js:32).""" |
| 14 | |
| 15 Args: | |
| 16 source_file: A file path. | |
| 17 line_number: The line in |file|. | |
| 18 """ | |
| 19 def __init__(self, source_file, line_number): | 14 def __init__(self, source_file, line_number): |
| 15 """ |
| 16 Args: |
| 17 source_file: A file path (as a string). |
| 18 line_number: The line in |file| (as an integer). |
| 19 """ |
| 20 self.file = source_file | 20 self.file = source_file |
| 21 self.line_number = int(line_number) | 21 self.line_number = int(line_number) |
| 22 | 22 |
| 23 | 23 |
| 24 class FileCache(object): | 24 class FileCache(object): |
| 25 """An in-memory cache to speed up reading the same files over and over. | 25 """An in-memory cache to speed up reading the same files over and over. |
| 26 | 26 |
| 27 Usage: | 27 Usage: |
| 28 FileCache.read(path_to_file) | 28 FileCache.read(path_to_file) |
| 29 """ | 29 """ |
| 30 | 30 |
| 31 _cache = defaultdict(str) | 31 _cache = defaultdict(str) |
| 32 | 32 |
| 33 @classmethod | 33 @classmethod |
| 34 def read(self, source_file): | 34 def read(self, source_file): |
| 35 """Read a file and return it as a string. | 35 """Read a file and return it as a string. |
| 36 | 36 |
| 37 Args: | 37 Args: |
| 38 source_file: a file to read and return the contents of. | 38 source_file: a file path (as a string) to read and return the contents. |
| 39 | 39 |
| 40 Returns: | 40 Returns: |
| 41 |file| as a string. | 41 The contents of |source_file| (as a string). |
| 42 """ | 42 """ |
| 43 abs_file = os.path.abspath(source_file) | 43 abs_file = os.path.abspath(source_file) |
| 44 self._cache[abs_file] = self._cache[abs_file] or open(abs_file, "r").read() | 44 self._cache[abs_file] = self._cache[abs_file] or open(abs_file, "r").read() |
| 45 return self._cache[abs_file] | 45 return self._cache[abs_file] |
| 46 | 46 |
| 47 | 47 |
| 48 class Processor(object): | 48 class Processor(object): |
| 49 """Processes resource files, inlining the contents of <include> tags, removing | 49 """Processes resource files, inlining the contents of <include> tags, removing |
| 50 <if> tags, and retaining original line info. | 50 <if> tags, and retaining original line info. |
| 51 | 51 |
| 52 For example | 52 For example |
| 53 | 53 |
| 54 1: /* blah.js */ | 54 1: /* blah.js */ |
| 55 2: <if expr="is_win"> | 55 2: <if expr="is_win"> |
| 56 3: <include src="win.js"> | 56 3: <include src="win.js"> |
| 57 4: </if> | 57 4: </if> |
| 58 | 58 |
| 59 would be turned into: | 59 would be turned into: |
| 60 | 60 |
| 61 1: /* blah.js */ | 61 1: /* blah.js */ |
| 62 2: | 62 2: |
| 63 3: /* win.js */ | 63 3: /* win.js */ |
| 64 4: alert('Ew; Windows.'); | 64 4: alert('Ew; Windows.'); |
| 65 5: | 65 5: |
| 66 | |
| 67 Args: | |
| 68 source_file: A file to process. | |
| 69 | |
| 70 Attributes: | |
| 71 contents: Expanded contents after inlining <include>s and stripping <if>s. | |
| 72 included_files: A list of files that were inlined via <include>. | |
| 73 """ | 66 """ |
| 74 | 67 |
| 75 _IF_TAGS_REG = "</?if[^>]*?>" | 68 _IF_TAGS_REG = "</?if[^>]*?>" |
| 76 _INCLUDE_REG = "<include[^>]+src=['\"]([^>]*)['\"]>" | 69 _INCLUDE_REG = "<include[^>]+src=['\"]([^>]*)['\"]>" |
| 77 | 70 |
| 78 def __init__(self, source_file): | 71 def __init__(self, source_file): |
| 79 self._included_files = set() | 72 """ |
| 73 Args: |
| 74 source_file: A file path to process (as a string). |
| 75 """ |
| 76 self.included_files = set() |
| 80 self._index = 0 | 77 self._index = 0 |
| 81 self._lines = self._get_file(source_file) | 78 self._lines = self._get_file(source_file) |
| 82 | 79 |
| 80 # Can't enumerate(self._lines) here because some lines are re-processed. |
| 83 while self._index < len(self._lines): | 81 while self._index < len(self._lines): |
| 84 current_line = self._lines[self._index] | 82 current_line = self._lines[self._index] |
| 85 match = re.search(self._INCLUDE_REG, current_line[2]) | 83 match = re.search(self._INCLUDE_REG, current_line[2]) |
| 86 if match: | 84 if match: |
| 87 file_dir = os.path.dirname(current_line[0]) | 85 file_dir = os.path.dirname(current_line[0]) |
| 88 file_name = os.path.abspath(os.path.join(file_dir, match.group(1))) | 86 file_name = os.path.abspath(os.path.join(file_dir, match.group(1))) |
| 89 if file_name not in self._included_files: | 87 if file_name not in self.included_files: |
| 90 self._include_file(file_name) | 88 self._include_file(file_name) |
| 91 continue # Stay on the same line. | 89 continue # Stay on the same line. |
| 92 else: | 90 else: |
| 93 # Found a duplicate <include>. Ignore and insert a blank line to | 91 # Found a duplicate <include>. Ignore and insert a blank line to |
| 94 # preserve line numbers. | 92 # preserve line numbers. |
| 95 self._lines[self._index] = self._lines[self._index][:2] + ("",) | 93 self._lines[self._index] = self._lines[self._index][:2] + ("",) |
| 96 self._index += 1 | 94 self._index += 1 |
| 97 | 95 |
| 98 for i, line in enumerate(self._lines): | 96 for i, line in enumerate(self._lines): |
| 99 self._lines[i] = line[:2] + (re.sub(self._IF_TAGS_REG, "", line[2]),) | 97 self._lines[i] = line[:2] + (re.sub(self._IF_TAGS_REG, "", line[2]),) |
| 100 | 98 |
| 101 self.contents = "\n".join(l[2] for l in self._lines) | 99 self.contents = "\n".join(l[2] for l in self._lines) |
| 102 | 100 |
| 103 # Returns a list of tuples in the format: (file, line number, line contents). | 101 # Returns a list of tuples in the format: (file, line number, line contents). |
| 104 def _get_file(self, source_file): | 102 def _get_file(self, source_file): |
| 105 lines = FileCache.read(source_file).splitlines() | 103 lines = FileCache.read(source_file).splitlines() |
| 106 return [(source_file, lnum + 1, line) for lnum, line in enumerate(lines)] | 104 return [(source_file, lnum + 1, line) for lnum, line in enumerate(lines)] |
| 107 | 105 |
| 108 def _include_file(self, source_file): | 106 def _include_file(self, source_file): |
| 109 self._included_files.add(source_file) | 107 self.included_files.add(source_file) |
| 110 f = self._get_file(source_file) | 108 f = self._get_file(source_file) |
| 111 self._lines = self._lines[:self._index] + f + self._lines[self._index + 1:] | 109 self._lines = self._lines[:self._index] + f + self._lines[self._index + 1:] |
| 112 | 110 |
| 113 def get_file_from_line(self, line_number): | 111 def get_file_from_line(self, line_number): |
| 114 """Get the original file and line number for an expanded file's line number. | 112 """Get the original file and line number for an expanded file's line number. |
| 115 | 113 |
| 116 Args: | 114 Args: |
| 117 line_number: A processed file's line number. | 115 line_number: A processed file's line number (as an integer or string). |
| 118 """ | 116 """ |
| 119 line_number = int(line_number) - 1 | 117 line_number = int(line_number) - 1 |
| 120 return LineNumber(self._lines[line_number][0], self._lines[line_number][1]) | 118 return LineNumber(self._lines[line_number][0], self._lines[line_number][1]) |
| 121 | |
| 122 @property | |
| 123 def included_files(self): | |
| 124 """A list of files that were inlined via <include>.""" | |
| 125 return self._included_files | |
| OLD | NEW |