# 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('(') 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): self(' {\n') with self.indent(2): yield self('}\n') @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)