xref: /aosp_15_r20/external/cronet/third_party/jni_zero/common.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2023 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Common logic needed by other modules."""
5
6import contextlib
7import filecmp
8import os
9import shutil
10import tempfile
11import pathlib
12import zipfile
13
14
15# Only some methods respect line length, so this is more of a best-effort
16# limit.
17_TARGET_LINE_LENGTH = 80
18
19
20class StringBuilder:
21
22  def __init__(self):
23    self._sb = []
24    self._indent = 0
25    self._start_of_line = True
26
27  def __call__(self, value):
28    lines = value.splitlines(keepends=True)
29    for line in lines:
30      if self._start_of_line and line != '\n':
31        self._sb.append(' ' * self._indent)
32      self._sb.append(line)
33      self._start_of_line = line[-1] == '\n'
34
35  def _cur_line_length(self):
36    ret = 0
37    for l in reversed(self._sb):
38      if l.endswith('\n'):
39        break
40      ret += len(l)
41    return ret
42
43  @contextlib.contextmanager
44  def _param_list_generator(self):
45    values = []
46    yield values
47    self.param_list(values)
48
49  def param_list(self, values=None):
50    if values is None:
51      return self._param_list_generator()
52
53    self('(')
54    punctuation_size = 2 * len(values) # punctuation: ", ()"
55    single_line_size = sum(len(v) for v in values) + punctuation_size
56    if self._cur_line_length() + single_line_size < _TARGET_LINE_LENGTH:
57      self(', '.join(values))
58    else:
59      self('\n')
60      with self.indent(4):
61        self(',\n'.join(values))
62    self(')')
63
64  @contextlib.contextmanager
65  def statement(self):
66    yield
67    self(';\n')
68
69  @contextlib.contextmanager
70  def block(self):
71    self(' {\n')
72    with self.indent(2):
73      yield
74    self('}\n')
75
76  @contextlib.contextmanager
77  def indent(self, amount):
78    self._indent += amount
79    yield
80    self._indent -= amount
81
82  def to_string(self):
83    return ''.join(self._sb)
84
85
86def capitalize(value):
87  return value[0].upper() + value[1:]
88
89
90def escape_class_name(fully_qualified_class):
91  """Returns an escaped string concatenating the Java package and class."""
92  escaped = fully_qualified_class.replace('_', '_1')
93  return escaped.replace('/', '_').replace('$', '_00024')
94
95
96@contextlib.contextmanager
97def atomic_output(path, mode='w+b'):
98  with tempfile.NamedTemporaryFile(mode, delete=False) as f:
99    try:
100      yield f
101    finally:
102      f.close()
103
104    if not (os.path.exists(path) and filecmp.cmp(f.name, path)):
105      pathlib.Path(path).parents[0].mkdir(parents=True, exist_ok=True)
106      shutil.move(f.name, path)
107    if os.path.exists(f.name):
108      os.unlink(f.name)
109
110
111def add_to_zip_hermetic(zip_file, zip_path, data=None):
112  zipinfo = zipfile.ZipInfo(filename=zip_path)
113  zipinfo.external_attr = 0o644 << 16
114  zipinfo.date_time = (2001, 1, 1, 0, 0, 0)
115  zip_file.writestr(zipinfo, data, zipfile.ZIP_STORED)
116