123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- # Copyright 2022 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.
- # Generetes a wrapper TS file around a source CSS file holding a Polymer style
- # module, or a Polymer <custom-style> holding CSS variable. Any metadata
- # necessary for populating the wrapper file are provided in the form of special
- # CSS comments. The ID of a style module is inferred from the filename, for
- # example foo_style.css will be held in a module with ID 'foo-style'.
- import argparse
- import io
- import re
- import shutil
- import sys
- import tempfile
- from os import path, getcwd, makedirs
- _HERE_PATH = path.dirname(__file__)
- _SRC_PATH = path.normpath(path.join(_HERE_PATH, '..', '..'))
- _CWD = getcwd()
- sys.path.append(path.join(_SRC_PATH, 'third_party', 'node'))
- import node
- _METADATA_START_REGEX = '#css_wrapper_metadata_start'
- _METADATA_END_REGEX = '#css_wrapper_metadata_end'
- _IMPORT_REGEX = '#import='
- _INCLUDE_REGEX = '#include='
- _TYPE_REGEX = '#type='
- _STYLE_TEMPLATE = """import \'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js\';
- %(imports)s
- const styleMod = document.createElement(\'dom-module\');
- styleMod.innerHTML = `
- <template>
- <style%(include)s>
- %(content)s
- </style>
- </template>
- `;
- styleMod.register(\'%(id)s\');"""
- _VARS_TEMPLATE = """import \'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js\';
- %(imports)s
- const $_documentContainer = document.createElement('template');
- $_documentContainer.innerHTML = `
- <custom-style>
- <style>
- %(content)s
- </style>
- </custom-style>
- `;
- document.head.appendChild($_documentContainer.content);"""
- def _parse_style_line(line, metadata):
- include_match = re.search(_INCLUDE_REGEX, line)
- if include_match:
- assert not metadata[
- 'include'], f'Found multiple "{_INCLUDE_REGEX}" lines. Only one should exist.'
- metadata['include'] = line[include_match.end():]
- import_match = re.search(_IMPORT_REGEX, line)
- if import_match:
- metadata['imports'].append(line[import_match.end():])
- def _parse_vars_line(line, metadata):
- import_match = re.search(_IMPORT_REGEX, line)
- if import_match:
- metadata['imports'].append(line[import_match.end():])
- def _extract_content(css_file, metadata, minified):
- with io.open(css_file, encoding='utf-8', mode='r') as f:
- # If minification is on, just grab the result from html-minifier's output.
- if minified:
- return f.read()
- # If minification is off, strip the special metadata comments from the
- # original file.
- lines = f.read().splitlines()
- return '\n'.join(lines[metadata['metadata_end_line'] + 1:])
- def _extract_metadata(css_file):
- metadata_start_line = -1
- metadata_end_line = -1
- metadata = {'type': None}
- with io.open(css_file, encoding='utf-8', mode='r') as f:
- lines = f.read().splitlines()
- for i, line in enumerate(lines):
- if metadata_start_line == -1:
- if _METADATA_START_REGEX in line:
- assert metadata_end_line == -1
- metadata_start_line = i
- else:
- assert metadata_end_line == -1
- if not metadata['type']:
- type_match = re.search(_TYPE_REGEX, line)
- if type_match:
- type = line[type_match.end():]
- assert type in ['style', 'vars']
- if type == 'style':
- id = path.splitext(path.basename(css_file))[0].replace('_', '-')
- metadata = {
- 'id': id,
- 'imports': [],
- 'include': None,
- 'metadata_end_line': -1,
- 'type': type,
- }
- elif type == 'vars':
- metadata = {
- 'imports': [],
- 'metadata_end_line': -1,
- 'type': type,
- }
- elif metadata['type'] == 'style':
- _parse_style_line(line, metadata)
- elif metadata['type'] == 'vars':
- _parse_vars_line(line, metadata)
- if _METADATA_END_REGEX in line:
- assert metadata_start_line > -1
- metadata_end_line = i
- metadata['metadata_end_line'] = metadata_end_line
- break
- assert metadata_start_line > -1
- assert metadata_end_line > -1
- return metadata
- def main(argv):
- parser = argparse.ArgumentParser()
- parser.add_argument('--in_folder', required=True)
- parser.add_argument('--out_folder', required=True)
- parser.add_argument('--in_files', required=True, nargs="*")
- parser.add_argument('--minify', action='store_true')
- parser.add_argument('--use_js', action='store_true')
- args = parser.parse_args(argv)
- in_folder = path.normpath(path.join(_CWD, args.in_folder))
- out_folder = path.normpath(path.join(_CWD, args.out_folder))
- extension = '.js' if args.use_js else '.ts'
- # The folder to be used to read the CSS files to be wrapped.
- wrapper_in_folder = in_folder
- if args.minify:
- # Minify the CSS files with html-minifier before generating the wrapper
- # .ts files.
- # Note: Passing all CSS files to html-minifier all at once because
- # passing them individually takes a lot longer.
- # Storing the output in a temporary folder, which is used further below when
- # creating the final wrapper files.
- tmp_out_dir = tempfile.mkdtemp(dir=out_folder)
- try:
- wrapper_in_folder = tmp_out_dir
- # Using the programmatic Node API to invoke html-minifier, because the
- # built-in command line API does not support explicitly specifying
- # multiple files to be processed, and only supports specifying an input
- # folder, which would lead to potentially processing unnecessary HTML
- # files that are not part of the build (stale), or handled by other
- # css_to_wrapper targets.
- node.RunNode(
- [path.join(_HERE_PATH, 'html_minifier.js'), in_folder, tmp_out_dir] +
- args.in_files)
- except RuntimeError as err:
- shutil.rmtree(tmp_out_dir)
- raise err
- def _urls_to_imports(urls):
- return '\n'.join(map(lambda i: f'import \'{i}\';', urls))
- for in_file in args.in_files:
- # Extract metadata from the original file, as the special metadata comments
- # only exist there.
- metadata = _extract_metadata(path.join(in_folder, in_file))
- # Extract the CSS content from either the original or the minified files.
- content = _extract_content(path.join(wrapper_in_folder, in_file), metadata,
- args.minify)
- wrapper = None
- if metadata['type'] == 'style':
- include = ''
- if metadata['include']:
- parsed_include = metadata['include']
- include = f' include="{parsed_include}"'
- wrapper = _STYLE_TEMPLATE % {
- 'imports': _urls_to_imports(metadata['imports']),
- 'content': content,
- 'include': include,
- 'id': metadata['id'],
- }
- elif metadata['type'] == 'vars':
- wrapper = _VARS_TEMPLATE % {
- 'imports': _urls_to_imports(metadata['imports']),
- 'content': content,
- }
- assert wrapper
- out_folder_for_file = path.join(out_folder, path.dirname(in_file))
- makedirs(out_folder_for_file, exist_ok=True)
- with io.open(path.join(out_folder, in_file) + extension, mode='wb') as f:
- f.write(wrapper.encode('utf-8'))
- if args.minify:
- # Delete the temporary folder that was holding minified CSS files, no
- # longer needed.
- shutil.rmtree(tmp_out_dir)
- return
- if __name__ == '__main__':
- main(sys.argv[1:])
|