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='
_SCHEME_REGEX = '#scheme='
_TYPE_REGEX = '#type='
_POLYMER_STYLE_TEMPLATE = """import {html} from \'%(scheme)s//resources/polymer/v3_0/polymer/polymer_bundled.min.js\';
%(imports)s
const styleMod = document.createElement(\'dom-module\');
styleMod.appendChild(html`
<template>
<style%(include)s>
%(content)s
</style>
</template>
`.content);
styleMod.register(\'%(id)s\');"""
_VARS_TEMPLATE = """%(imports)s
export {};
const sheet = new CSSStyleSheet();
sheet.replaceSync(`%(content)s`);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];"""
_LIT_STYLE_TEMPLATE = """import {css, CSSResultGroup} from '%(scheme)s//resources/lit/v3_0/lit.rollup.js';
%(imports)s
let instance: CSSResultGroup|null = null;
export function getCss() {
return instance || (instance = [...[%(deps)s], css`%(content)s`]);
}"""
_LIT_VARS_IOS_TEMPLATE = """%(imports)s
export {};
const css = `%(content)s`;
try {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
} catch (e) {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
}
"""
_TEMPLATE_MAP = {
'style': _POLYMER_STYLE_TEMPLATE,
'style-lit': _LIT_STYLE_TEMPLATE,
'vars': _VARS_TEMPLATE,
'vars-lit': _VARS_TEMPLATE,
}
_LIT_SUFFIX = "_lit.css"
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():]
_parse_import_line(line, metadata)
def _parse_import_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 minified:
return f.read()
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, 'scheme': 'default'}
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', 'style-lit', 'vars', 'vars-lit']
if type == 'style':
id = path.splitext(path.basename(css_file))[0].replace('_', '-')
metadata = {
'id': id,
'imports': [],
'include': None,
'metadata_end_line': -1,
'scheme': metadata['scheme'],
'type': type,
}
elif type == 'style-lit':
metadata = {
'imports': [],
'include': None,
'metadata_end_line': -1,
'scheme': metadata['scheme'],
'type': type,
}
elif type == 'vars' or type == 'vars-lit':
metadata = {
'imports': [],
'metadata_end_line': -1,
'scheme': metadata['scheme'],
'type': type,
}
elif metadata['type'] == 'style' or metadata['type'] == 'style-lit':
_parse_style_line(line, metadata)
elif metadata['type'] == 'vars' or metadata['type'] == 'vars-lit':
_parse_import_line(line, metadata)
if metadata['scheme'] == 'default':
scheme_match = re.search(_SCHEME_REGEX, line)
if scheme_match:
scheme = line[scheme_match.end():]
assert scheme in ['chrome', 'relative', 'arkweb']
metadata['scheme'] = scheme
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')
parser.add_argument('--is_ios', action='store_true')
parser.add_argument('--arkweb_as_default_scheme', 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'
wrapper_in_folder = in_folder
if args.minify:
tmp_out_dir = tempfile.mkdtemp(dir=out_folder)
try:
wrapper_in_folder = tmp_out_dir
node.RunNode(
[path.join(_HERE_PATH, 'css_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))
def _dash_case_to_title_case(string):
return string.replace('-', ' ').title().replace(' ', '')
def _urls_to_imports_lit(metadata):
if metadata['include'] is None:
return '\n'.join(map(lambda i: f'import \'{i}\';', metadata['imports']))
imports = []
style_deps = metadata['include'].split()
for dep in metadata['imports']:
style_id = path.split(dep)[1].replace('_', '-').replace('.css.js', '')
if style_id in style_deps:
alias = _dash_case_to_title_case(style_id)
imports.append(f'import {{getCss as get{alias}}} from \'{dep}\';')
else:
imports.append(f'import \'{dep}\';')
return '\n'.join(imports)
def _deps_to_function_calls(style_deps):
return ','.join(
map(lambda d: 'get' + _dash_case_to_title_case(d) + '()', style_deps))
for in_file in args.in_files:
metadata = _extract_metadata(path.join(in_folder, in_file))
content = ''
lit_in_file = in_file.replace('.css', _LIT_SUFFIX)
if metadata['type'] == 'style' and lit_in_file in args.in_files:
lit_metadata = _extract_metadata(path.join(in_folder, lit_in_file))
content = _extract_content(path.join(wrapper_in_folder, lit_in_file),
lit_metadata, args.minify)
else:
content = _extract_content(path.join(wrapper_in_folder, in_file),
metadata, args.minify)
if not content:
assert metadata['type'] == 'style-lit' and metadata['include'], \
'Unexpected empty CSS file found: ' + in_file
scheme = None
if metadata['scheme'] in ['default', 'chrome']:
scheme = 'chrome:'
elif metadata['scheme'] == 'relative':
scheme = ''
if args.arkweb_as_default_scheme and metadata['scheme'] == 'default':
scheme = 'arkweb:'
substitutions = None
if metadata['type'] == 'style':
include = ''
if metadata['include']:
parsed_include = metadata['include']
include = f' include="{parsed_include}"'
substitutions = {
'imports': _urls_to_imports(metadata['imports']),
'content': content,
'include': include,
'id': metadata['id'],
'scheme': scheme,
}
elif metadata['type'] == 'style-lit':
substitutions = {
'imports': _urls_to_imports_lit(metadata),
'deps': '' if metadata['include'] is None else \
_deps_to_function_calls(metadata['include'].split()),
'content': content,
'scheme': scheme,
}
elif metadata['type'] == 'vars' or metadata['type'] == 'vars-lit':
substitutions = {
'imports': _urls_to_imports(metadata['imports']),
'content': content,
'scheme': scheme,
}
assert substitutions
template = _TEMPLATE_MAP[metadata['type']]
if args.is_ios and metadata['type'] == 'vars-lit':
template = _LIT_VARS_IOS_TEMPLATE
wrapper = template % substitutions
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:
shutil.rmtree(tmp_out_dir)
return
if __name__ == '__main__':
main(sys.argv[1:])