105 lines
2.5 KiB
Python
105 lines
2.5 KiB
Python
|
|
# Copyright 2019 The Chromium Authors
|
||
|
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
|
# found in the LICENSE file.
|
||
|
|
|
||
|
|
"""Basic .ini encoding and decoding.
|
||
|
|
|
||
|
|
The basic element in an ini file is the key. Every key is constructed by a name
|
||
|
|
and a value, delimited by an equals sign (=).
|
||
|
|
|
||
|
|
Keys may be grouped into sections. The secetion name will be a line by itself,
|
||
|
|
in square brackets ([ and ]). All keys after the section are associated with
|
||
|
|
that section until another section occurs.
|
||
|
|
|
||
|
|
Keys that are not under any section are considered at the top level.
|
||
|
|
|
||
|
|
Section and key names are case sensitive.
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
import contextlib
|
||
|
|
import os
|
||
|
|
|
||
|
|
|
||
|
|
def add_key(line, config, strict=True):
|
||
|
|
key, val = line.split('=', 1)
|
||
|
|
key = key.strip()
|
||
|
|
val = val.strip()
|
||
|
|
if strict and key in config:
|
||
|
|
raise ValueError('Multiple entries present for key "%s"' % key)
|
||
|
|
config[key] = val
|
||
|
|
|
||
|
|
|
||
|
|
def loads(ini_str, strict=True):
|
||
|
|
"""Deserialize int_str to a dict (nested dict when has sections) object.
|
||
|
|
|
||
|
|
Duplicated sections will merge their keys.
|
||
|
|
|
||
|
|
When there are multiple entries for a key, at the top level, or under the
|
||
|
|
same section:
|
||
|
|
- If strict is true, ValueError will be raised.
|
||
|
|
- If strict is false, only the last occurrence will be stored.
|
||
|
|
"""
|
||
|
|
ret = {}
|
||
|
|
section = None
|
||
|
|
for line in ini_str.splitlines():
|
||
|
|
# Empty line
|
||
|
|
if not line:
|
||
|
|
continue
|
||
|
|
# Section line
|
||
|
|
if line[0] == '[' and line[-1] == ']':
|
||
|
|
section = line[1:-1]
|
||
|
|
if section not in ret:
|
||
|
|
ret[section] = {}
|
||
|
|
# Key line
|
||
|
|
else:
|
||
|
|
config = ret if section is None else ret[section]
|
||
|
|
add_key(line, config, strict=strict)
|
||
|
|
|
||
|
|
return ret
|
||
|
|
|
||
|
|
|
||
|
|
def load(fp):
|
||
|
|
return loads(fp.read())
|
||
|
|
|
||
|
|
|
||
|
|
def dumps(obj):
|
||
|
|
results = []
|
||
|
|
key_str = ''
|
||
|
|
|
||
|
|
for k, v in sorted(obj.items()):
|
||
|
|
if isinstance(v, dict):
|
||
|
|
results.append('[%s]\n' % k + dumps(v))
|
||
|
|
else:
|
||
|
|
key_str += '%s = %s\n' % (k, str(v))
|
||
|
|
|
||
|
|
# Insert key_str at the first position, before any sections
|
||
|
|
if key_str:
|
||
|
|
results.insert(0, key_str)
|
||
|
|
|
||
|
|
return '\n'.join(results)
|
||
|
|
|
||
|
|
|
||
|
|
def dump(obj, fp):
|
||
|
|
fp.write(dumps(obj))
|
||
|
|
|
||
|
|
|
||
|
|
@contextlib.contextmanager
|
||
|
|
def update_ini_file(ini_file_path):
|
||
|
|
"""Load and update the contents of an ini file.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
ini_file_path: A string containing the absolute path of the ini file.
|
||
|
|
Yields:
|
||
|
|
The contents of the file, as a dict
|
||
|
|
"""
|
||
|
|
ini_contents = {}
|
||
|
|
if os.path.exists(ini_file_path):
|
||
|
|
with open(ini_file_path) as ini_file:
|
||
|
|
ini_contents = load(ini_file)
|
||
|
|
|
||
|
|
yield ini_contents
|
||
|
|
|
||
|
|
with open(ini_file_path, 'w') as ini_file:
|
||
|
|
dump(ini_contents, ini_file)
|