xref: /aosp_15_r20/external/angle/build/android/pylib/local/emulator/ini.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1# Copyright 2019 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
5"""Basic .ini encoding and decoding.
6
7The basic element in an ini file is the key. Every key is constructed by a name
8and a value, delimited by an equals sign (=).
9
10Keys may be grouped into sections. The secetion name will be a line by itself,
11in square brackets ([ and ]). All keys after the section are associated with
12that section until another section occurs.
13
14Keys that are not under any section are considered at the top level.
15
16Section and key names are case sensitive.
17"""
18
19
20import contextlib
21import os
22
23
24def add_key(line, config, strict=True):
25  key, val = line.split('=', 1)
26  key = key.strip()
27  val = val.strip()
28  if strict and key in config:
29    raise ValueError('Multiple entries present for key "%s"' % key)
30  config[key] = val
31
32
33def loads(ini_str, strict=True):
34  """Deserialize int_str to a dict (nested dict when has sections) object.
35
36  Duplicated sections will merge their keys.
37
38  When there are multiple entries for a key, at the top level, or under the
39  same section:
40   - If strict is true, ValueError will be raised.
41   - If strict is false, only the last occurrence will be stored.
42  """
43  ret = {}
44  section = None
45  for line in ini_str.splitlines():
46    # Empty line
47    if not line:
48      continue
49    # Section line
50    if line[0] == '[' and line[-1] == ']':
51      section = line[1:-1]
52      if section not in ret:
53        ret[section] = {}
54    # Key line
55    else:
56      config = ret if section is None else ret[section]
57      add_key(line, config, strict=strict)
58
59  return ret
60
61
62def load(fp):
63  return loads(fp.read())
64
65
66def dumps(obj):
67  results = []
68  key_str = ''
69
70  for k, v in sorted(obj.items()):
71    if isinstance(v, dict):
72      results.append('[%s]\n' % k + dumps(v))
73    else:
74      key_str += '%s = %s\n' % (k, str(v))
75
76  # Insert key_str at the first position, before any sections
77  if key_str:
78    results.insert(0, key_str)
79
80  return '\n'.join(results)
81
82
83def dump(obj, fp):
84  fp.write(dumps(obj))
85
86
87@contextlib.contextmanager
88def update_ini_file(ini_file_path):
89  """Load and update the contents of an ini file.
90
91  Args:
92    ini_file_path: A string containing the absolute path of the ini file.
93  Yields:
94    The contents of the file, as a dict
95  """
96  ini_contents = {}
97  if os.path.exists(ini_file_path):
98    with open(ini_file_path) as ini_file:
99      ini_contents = load(ini_file)
100
101  yield ini_contents
102
103  with open(ini_file_path, 'w') as ini_file:
104    dump(ini_contents, ini_file)
105