# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Common logic needed by other modules."""
import contextlib
import filecmp
import os
import shutil
import tempfile
import pathlib
import zipfile
# Only some methods respect line length, so this is more of a best-effort
# limit.
_TARGET_LINE_LENGTH = 80
class StringBuilder:
def __init__(self):
self._sb = []
self._indent = 0
self._start_of_line = True
def __call__(self, value):
lines = value.splitlines(keepends=True)
for line in lines:
if self._start_of_line and line != '\n':
self._sb.append(' ' * self._indent)
self._sb.append(line)
self._start_of_line = line[-1] == '\n'
def _cur_line_length(self):
ret = 0
for l in reversed(self._sb):
if l.endswith('\n'):
break
ret += len(l)
return ret
@contextlib.contextmanager
def _param_list_generator(self):
values = []
yield values
self.param_list(values)
def param_list(self, values=None):
if values is None:
return self._param_list_generator()
self('(')
if values:
punctuation_size = 2 * len(values) # punctuation: ", ()"
single_line_size = sum(len(v) for v in values) + punctuation_size
if self._cur_line_length() + single_line_size < _TARGET_LINE_LENGTH:
self(', '.join(values))
else:
self('\n')
with self.indent(4):
self(',\n'.join(values))
self(')')
@contextlib.contextmanager
def statement(self):
yield
self(';\n')
@contextlib.contextmanager
def block(self, start=' {\n', end='}\n'):
self(start)
with self.indent(2):
yield
self(end)
@contextlib.contextmanager
def indent(self, amount):
self._indent += amount
yield
self._indent -= amount
def to_string(self):
return ''.join(self._sb)
def capitalize(value):
return value[0].upper() + value[1:]
def escape_class_name(fully_qualified_class):
"""Returns an escaped string concatenating the Java package and class."""
escaped = fully_qualified_class.replace('_', '_1')
return escaped.replace('/', '_').replace('$', '_00024')
@contextlib.contextmanager
def atomic_output(path, mode='w+b'):
with tempfile.NamedTemporaryFile(mode, delete=False) as f:
try:
yield f
finally:
f.close()
if not (os.path.exists(path) and filecmp.cmp(f.name, path)):
pathlib.Path(path).parents[0].mkdir(parents=True, exist_ok=True)
shutil.move(f.name, path)
if os.path.exists(f.name):
os.unlink(f.name)
def add_to_zip_hermetic(zip_file, zip_path, data=None):
zipinfo = zipfile.ZipInfo(filename=zip_path)
zipinfo.external_attr = 0o644 << 16
zipinfo.date_time = (2001, 1, 1, 0, 0, 0)
zip_file.writestr(zipinfo, data, zipfile.ZIP_STORED)
def should_rename_package(package_name, filter_list_string):
# If the filter list is empty, all packages should be renamed.
if not filter_list_string:
return True
return any(
package_name.startswith(pkg_prefix)
for pkg_prefix in filter_list_string.split(':'))