mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-25 09:37:37 +00:00
Add YAML serialization of options
This uses ruamel.yaml. The library seems well-supported, and can do in-place modification of config files that retains user comments and file structure.
This commit is contained in:
parent
c94cd512d1
commit
d74cac265a
@ -5,6 +5,9 @@ import inspect
|
|||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import weakref
|
import weakref
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ruamel.yaml
|
||||||
|
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy.utils import typecheck
|
from mitmproxy.utils import typecheck
|
||||||
@ -171,6 +174,73 @@ class OptManager(metaclass=_DefaultsMeta):
|
|||||||
if getattr(self, option) != self._defaults[option]:
|
if getattr(self, option) != self._defaults[option]:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def save(self, path, defaults=False):
|
||||||
|
"""
|
||||||
|
Save to path. If the destination file exists, modify it in-place.
|
||||||
|
"""
|
||||||
|
if os.path.exists(path) and os.path.isfile(path):
|
||||||
|
data = open(path, "r").read()
|
||||||
|
else:
|
||||||
|
data = ""
|
||||||
|
data = self.serialize(data, defaults)
|
||||||
|
fp = open(path, "w")
|
||||||
|
fp.write(data)
|
||||||
|
|
||||||
|
def serialize(self, text, defaults=False):
|
||||||
|
"""
|
||||||
|
Performs a round-trip serialization. If text is not None, it is
|
||||||
|
treated as a previous serialization that should be modified
|
||||||
|
in-place.
|
||||||
|
|
||||||
|
- If "defaults" is False, only options with non-default values are
|
||||||
|
serialized. Default values in text are preserved.
|
||||||
|
- Unknown options in text are removed.
|
||||||
|
- Raises OptionsError if text is invalid.
|
||||||
|
"""
|
||||||
|
data = self._load(text)
|
||||||
|
for k in self.keys():
|
||||||
|
if defaults or self.has_changed(k):
|
||||||
|
data[k] = getattr(self, k)
|
||||||
|
for k in list(data.keys()):
|
||||||
|
if k not in self._opts:
|
||||||
|
del data[k]
|
||||||
|
return ruamel.yaml.round_trip_dump(data)
|
||||||
|
|
||||||
|
def _load(self, text):
|
||||||
|
if not text:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
data = ruamel.yaml.load(text, ruamel.yaml.Loader)
|
||||||
|
except ruamel.yaml.error.YAMLError as v:
|
||||||
|
snip = v.problem_mark.get_snippet()
|
||||||
|
raise exceptions.OptionsError(
|
||||||
|
"Config error at line %s:\n%s\n%s" %
|
||||||
|
(v.problem_mark.line+1, snip, v.problem)
|
||||||
|
)
|
||||||
|
if isinstance(data, str):
|
||||||
|
raise exceptions.OptionsError("Config error - no keys found.")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def load(self, text):
|
||||||
|
"""
|
||||||
|
Load configuration from text, over-writing options already set in
|
||||||
|
this object. May raise OptionsError if the config file is invalid.
|
||||||
|
"""
|
||||||
|
data = self._load(text)
|
||||||
|
for k, v in data.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def load_paths(self, *paths):
|
||||||
|
"""
|
||||||
|
Load paths in order. Each path takes precedence over the previous
|
||||||
|
path. Paths that don't exist are ignored, errors raise an
|
||||||
|
OptionsError.
|
||||||
|
"""
|
||||||
|
for p in paths:
|
||||||
|
if os.path.exists(p) and os.path.isfile(p):
|
||||||
|
txt = open(p, "r").read()
|
||||||
|
self.load(txt)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
options = pprint.pformat(self._opts, indent=4).strip(" {}")
|
options = pprint.pformat(self._opts, indent=4).strip(" {}")
|
||||||
if "\n" in options:
|
if "\n" in options:
|
||||||
|
1
setup.py
1
setup.py
@ -79,6 +79,7 @@ setup(
|
|||||||
"pyparsing>=2.1.3, <2.2",
|
"pyparsing>=2.1.3, <2.2",
|
||||||
"pyperclip>=1.5.22, <1.6",
|
"pyperclip>=1.5.22, <1.6",
|
||||||
"requests>=2.9.1, <3",
|
"requests>=2.9.1, <3",
|
||||||
|
"ruamel.yaml>=0.13.2, <0.14",
|
||||||
"tornado>=4.3, <4.5",
|
"tornado>=4.3, <4.5",
|
||||||
"urwid>=1.3.1, <1.4",
|
"urwid>=1.3.1, <1.4",
|
||||||
"watchdog>=0.8.3, <0.9",
|
"watchdog>=0.8.3, <0.9",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import copy
|
import copy
|
||||||
|
import os
|
||||||
|
|
||||||
|
from mitmproxy import options
|
||||||
from mitmproxy import optmanager
|
from mitmproxy import optmanager
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy.test import tutils
|
from mitmproxy.test import tutils
|
||||||
@ -24,7 +26,7 @@ class TD2(TD):
|
|||||||
def __init__(self, *, three="dthree", four="dfour", **kwargs):
|
def __init__(self, *, three="dthree", four="dfour", **kwargs):
|
||||||
self.three = three
|
self.three = three
|
||||||
self.four = four
|
self.four = four
|
||||||
super().__init__(**kwargs)
|
super().__init__(three=three, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def test_defaults():
|
def test_defaults():
|
||||||
@ -167,3 +169,54 @@ def test_repr():
|
|||||||
'one': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
'one': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
'two': None
|
'two': None
|
||||||
})"""
|
})"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_serialize():
|
||||||
|
o = TD2()
|
||||||
|
o.three = "set"
|
||||||
|
assert "dfour" in o.serialize(None, defaults=True)
|
||||||
|
|
||||||
|
data = o.serialize(None)
|
||||||
|
assert "dfour" not in data
|
||||||
|
|
||||||
|
o2 = TD2()
|
||||||
|
o2.load(data)
|
||||||
|
assert o2 == o
|
||||||
|
|
||||||
|
t = """
|
||||||
|
unknown: foo
|
||||||
|
"""
|
||||||
|
data = o.serialize(t)
|
||||||
|
o2 = TD2()
|
||||||
|
o2.load(data)
|
||||||
|
assert o2 == o
|
||||||
|
|
||||||
|
t = "invalid: foo\ninvalid"
|
||||||
|
tutils.raises("config error", o2.load, t)
|
||||||
|
|
||||||
|
t = "invalid"
|
||||||
|
tutils.raises("config error", o2.load, t)
|
||||||
|
|
||||||
|
t = ""
|
||||||
|
o2.load(t)
|
||||||
|
|
||||||
|
|
||||||
|
def test_serialize_defaults():
|
||||||
|
o = options.Options()
|
||||||
|
assert o.serialize(None, defaults=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_saving():
|
||||||
|
o = TD2()
|
||||||
|
o.three = "set"
|
||||||
|
with tutils.tmpdir() as tdir:
|
||||||
|
dst = os.path.join(tdir, "conf")
|
||||||
|
o.save(dst, defaults=True)
|
||||||
|
|
||||||
|
o2 = TD2()
|
||||||
|
o2.load_paths(dst)
|
||||||
|
o2.three = "foo"
|
||||||
|
o2.save(dst, defaults=True)
|
||||||
|
|
||||||
|
o.load_paths(dst)
|
||||||
|
assert o.three == "foo"
|
||||||
|
Loading…
Reference in New Issue
Block a user