omlish 0.0.0.dev81__py3-none-any.whl → 0.0.0.dev83__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/.manifests.json +3 -3
- omlish/__about__.py +2 -2
- omlish/dataclasses/impl/__init__.py +8 -0
- omlish/dataclasses/impl/params.py +3 -0
- omlish/dataclasses/impl/slots.py +61 -7
- omlish/formats/json/__init__.py +8 -1
- omlish/formats/json/backends/__init__.py +7 -0
- omlish/formats/json/backends/base.py +38 -0
- omlish/formats/json/backends/default.py +10 -0
- omlish/formats/json/backends/jiter.py +25 -0
- omlish/formats/json/backends/orjson.py +46 -2
- omlish/formats/json/backends/std.py +39 -0
- omlish/formats/json/backends/ujson.py +49 -0
- omlish/formats/json/cli/__init__.py +0 -0
- omlish/formats/json/{cli.py → cli/cli.py} +50 -48
- omlish/formats/json/cli/formats.py +64 -0
- omlish/formats/json/consts.py +22 -0
- omlish/formats/json/encoding.py +17 -0
- omlish/formats/json/json.py +9 -39
- omlish/formats/json/render.py +49 -28
- omlish/formats/json/stream/__init__.py +0 -0
- omlish/formats/json/stream/build.py +113 -0
- omlish/formats/json/{stream.py → stream/lex.py} +68 -172
- omlish/formats/json/stream/parse.py +244 -0
- omlish/formats/json/stream/render.py +119 -0
- omlish/formats/xml.py +63 -0
- omlish/genmachine.py +14 -2
- omlish/marshal/base.py +2 -0
- omlish/marshal/newtypes.py +24 -0
- omlish/marshal/standard.py +4 -0
- omlish/reflect/__init__.py +1 -0
- omlish/reflect/types.py +6 -1
- {omlish-0.0.0.dev81.dist-info → omlish-0.0.0.dev83.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev81.dist-info → omlish-0.0.0.dev83.dist-info}/RECORD +39 -26
- /omlish/formats/json/{__main__.py → cli/__main__.py} +0 -0
- {omlish-0.0.0.dev81.dist-info → omlish-0.0.0.dev83.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev81.dist-info → omlish-0.0.0.dev83.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev81.dist-info → omlish-0.0.0.dev83.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev81.dist-info → omlish-0.0.0.dev83.dist-info}/top_level.txt +0 -0
omlish/.manifests.json
CHANGED
@@ -36,14 +36,14 @@
|
|
36
36
|
}
|
37
37
|
},
|
38
38
|
{
|
39
|
-
"module": ".formats.json.__main__",
|
39
|
+
"module": ".formats.json.cli.__main__",
|
40
40
|
"attr": "_CLI_MODULE",
|
41
|
-
"file": "omlish/formats/json/__main__.py",
|
41
|
+
"file": "omlish/formats/json/cli/__main__.py",
|
42
42
|
"line": 1,
|
43
43
|
"value": {
|
44
44
|
"$omdev.cli.types.CliModule": {
|
45
45
|
"cmd_name": "json",
|
46
|
-
"mod_name": "omlish.formats.json.__main__"
|
46
|
+
"mod_name": "omlish.formats.json.cli.__main__"
|
47
47
|
}
|
48
48
|
}
|
49
49
|
},
|
omlish/__about__.py
CHANGED
@@ -22,4 +22,12 @@ TODO:
|
|
22
22
|
- enums
|
23
23
|
- nodal
|
24
24
|
- embedding? forward kwargs in general? or only for replace?
|
25
|
+
|
26
|
+
TODO refs:
|
27
|
+
- batch up exec calls
|
28
|
+
- https://github.com/python/cpython/commit/8945b7ff55b87d11c747af2dad0e3e4d631e62d6
|
29
|
+
- add doc parameter to dataclasses.field
|
30
|
+
- https://github.com/python/cpython/commit/9c7657f09914254724683d91177aed7947637be5
|
31
|
+
- add decorator argument to make_dataclass
|
32
|
+
- https://github.com/python/cpython/commit/3e3a4d231518f91ff2f3c5a085b3849e32f1d548
|
25
33
|
"""
|
omlish/dataclasses/impl/slots.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import dataclasses as dc
|
2
|
+
import inspect
|
2
3
|
import itertools
|
4
|
+
import types
|
3
5
|
|
4
6
|
|
5
7
|
MISSING = dc.MISSING
|
@@ -25,7 +27,7 @@ def _get_slots(cls):
|
|
25
27
|
slots = []
|
26
28
|
if getattr(cls, '__weakrefoffset__', -1) != 0:
|
27
29
|
slots.append('__weakref__')
|
28
|
-
if getattr(cls, '
|
30
|
+
if getattr(cls, '__dictoffset__', -1) != 0:
|
29
31
|
slots.append('__dict__')
|
30
32
|
yield from slots
|
31
33
|
case str(slot):
|
@@ -37,44 +39,96 @@ def _get_slots(cls):
|
|
37
39
|
raise TypeError(f"Slots of '{cls.__name__}' cannot be determined")
|
38
40
|
|
39
41
|
|
42
|
+
def _update_func_cell_for__class__(f, oldcls, newcls):
|
43
|
+
# Returns True if we update a cell, else False.
|
44
|
+
if f is None:
|
45
|
+
# f will be None in the case of a property where not all of fget, fset, and fdel are used. Nothing to do in
|
46
|
+
# that case.
|
47
|
+
return False
|
48
|
+
try:
|
49
|
+
idx = f.__code__.co_freevars.index('__class__')
|
50
|
+
except ValueError:
|
51
|
+
# This function doesn't reference __class__, so nothing to do.
|
52
|
+
return False
|
53
|
+
# Fix the cell to point to the new class, if it's already pointing at the old class. I'm not convinced that the "is
|
54
|
+
# oldcls" test is needed, but other than performance can't hurt.
|
55
|
+
closure = f.__closure__[idx]
|
56
|
+
if closure.cell_contents is oldcls:
|
57
|
+
closure.cell_contents = newcls
|
58
|
+
return True
|
59
|
+
return False
|
60
|
+
|
61
|
+
|
40
62
|
def add_slots(
|
41
63
|
cls: type,
|
42
64
|
is_frozen: bool,
|
43
65
|
weakref_slot: bool,
|
44
66
|
) -> type:
|
67
|
+
# Need to create a new class, since we can't set __slots__ after a class has been created, and the @dataclass
|
68
|
+
# decorator is called after the class is created.
|
69
|
+
|
70
|
+
# Make sure __slots__ isn't already set.
|
45
71
|
if '__slots__' in cls.__dict__:
|
46
72
|
raise TypeError(f'{cls.__name__} already specifies __slots__')
|
47
73
|
|
74
|
+
# Create a new dict for our new class.
|
48
75
|
cls_dict = dict(cls.__dict__)
|
49
76
|
field_names = tuple(f.name for f in dc.fields(cls)) # noqa
|
50
77
|
|
78
|
+
# Make sure slots don't overlap with those in base classes.
|
51
79
|
inherited_slots = set(itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1])))
|
52
80
|
|
81
|
+
# The slots for our class. Remove slots from our base classes. Add '__weakref__' if weakref_slot was given, unless
|
82
|
+
# it is already present.
|
53
83
|
cls_dict['__slots__'] = tuple(
|
54
84
|
itertools.filterfalse(
|
55
85
|
inherited_slots.__contains__,
|
56
86
|
itertools.chain(
|
57
87
|
field_names,
|
88
|
+
# gh-93521: '__weakref__' also needs to be filtered out if already present in inherited_slots
|
58
89
|
('__weakref__',) if weakref_slot else (),
|
59
90
|
),
|
60
91
|
),
|
61
92
|
)
|
62
93
|
|
63
94
|
for field_name in field_names:
|
95
|
+
# Remove our attributes, if present. They'll still be available in _MARKER.
|
64
96
|
cls_dict.pop(field_name, None)
|
65
97
|
|
98
|
+
# Remove __dict__ itself.
|
66
99
|
cls_dict.pop('__dict__', None)
|
100
|
+
|
101
|
+
# Clear existing `__weakref__` descriptor, it belongs to a previous type:
|
67
102
|
cls_dict.pop('__weakref__', None)
|
68
103
|
|
69
104
|
qualname = getattr(cls, '__qualname__', None)
|
70
|
-
|
105
|
+
newcls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
|
71
106
|
if qualname is not None:
|
72
|
-
|
107
|
+
newcls.__qualname__ = qualname
|
73
108
|
|
74
109
|
if is_frozen:
|
110
|
+
# Need this for pickling frozen classes with slots.
|
75
111
|
if '__getstate__' not in cls_dict:
|
76
|
-
|
112
|
+
newcls.__getstate__ = _dataclass_getstate # type: ignore
|
77
113
|
if '__setstate__' not in cls_dict:
|
78
|
-
|
79
|
-
|
80
|
-
|
114
|
+
newcls.__setstate__ = _dataclass_setstate # type: ignore
|
115
|
+
|
116
|
+
# Fix up any closures which reference __class__. This is used to fix zero argument super so that it points to the
|
117
|
+
# correct class (the newly created one, which we're returning) and not the original class. We can break out of this
|
118
|
+
# loop as soon as we make an update, since all closures for a class will share a given cell.
|
119
|
+
for member in newcls.__dict__.values():
|
120
|
+
# If this is a wrapped function, unwrap it.
|
121
|
+
member = inspect.unwrap(member)
|
122
|
+
if isinstance(member, types.FunctionType):
|
123
|
+
if _update_func_cell_for__class__(member, cls, newcls):
|
124
|
+
break
|
125
|
+
|
126
|
+
elif isinstance(member, property):
|
127
|
+
if (
|
128
|
+
_update_func_cell_for__class__(member.fget, cls, newcls) or
|
129
|
+
_update_func_cell_for__class__(member.fset, cls, newcls) or
|
130
|
+
_update_func_cell_for__class__(member.fdel, cls, newcls)
|
131
|
+
):
|
132
|
+
break
|
133
|
+
|
134
|
+
return newcls
|
omlish/formats/json/__init__.py
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
from .
|
1
|
+
from .consts import ( # noqa
|
2
2
|
COMPACT_KWARGS,
|
3
3
|
COMPACT_SEPARATORS,
|
4
4
|
PRETTY_INDENT,
|
5
5
|
PRETTY_KWARGS,
|
6
|
+
)
|
7
|
+
|
8
|
+
from .encoding import ( # noqa
|
9
|
+
decodes,
|
6
10
|
detect_encoding,
|
11
|
+
)
|
12
|
+
|
13
|
+
from .json import ( # noqa
|
7
14
|
dump,
|
8
15
|
dump_compact,
|
9
16
|
dump_pretty,
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import abc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .... import lang
|
5
|
+
|
6
|
+
|
7
|
+
class Backend(lang.Abstract):
|
8
|
+
@abc.abstractmethod
|
9
|
+
def dump(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
10
|
+
raise NotImplementedError
|
11
|
+
|
12
|
+
@abc.abstractmethod
|
13
|
+
def dumps(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
14
|
+
raise NotImplementedError
|
15
|
+
|
16
|
+
@abc.abstractmethod
|
17
|
+
def load(self, fp: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
18
|
+
raise NotImplementedError
|
19
|
+
|
20
|
+
@abc.abstractmethod
|
21
|
+
def loads(self, s: str | bytes | bytearray, **kwargs: ta.Any) -> ta.Any:
|
22
|
+
raise NotImplementedError
|
23
|
+
|
24
|
+
@abc.abstractmethod
|
25
|
+
def dump_pretty(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
26
|
+
raise NotImplementedError
|
27
|
+
|
28
|
+
@abc.abstractmethod
|
29
|
+
def dumps_pretty(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
30
|
+
raise NotImplementedError
|
31
|
+
|
32
|
+
@abc.abstractmethod
|
33
|
+
def dump_compact(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
34
|
+
raise NotImplementedError
|
35
|
+
|
36
|
+
@abc.abstractmethod
|
37
|
+
def dumps_compact(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
38
|
+
raise NotImplementedError
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""
|
2
|
+
from_json(
|
3
|
+
json_data: bytes,
|
4
|
+
/,
|
5
|
+
*,
|
6
|
+
allow_inf_nan: bool = True,
|
7
|
+
cache_mode: Literal[True, False, "all", "keys", "none"] = "all",
|
8
|
+
partial_mode: Literal[True, False, "off", "on", "trailing-strings"] = False,
|
9
|
+
catch_duplicate_keys: bool = False,
|
10
|
+
lossless_floats: bool = False,
|
11
|
+
) -> Any:
|
12
|
+
json_data: The JSON data to parse
|
13
|
+
allow_inf_nan: Whether to allow infinity (`Infinity` an `-Infinity`) and `NaN` values to float fields.
|
14
|
+
Defaults to True.
|
15
|
+
cache_mode: cache Python strings to improve performance at the cost of some memory usage
|
16
|
+
- True / 'all' - cache all strings
|
17
|
+
- 'keys' - cache only object keys
|
18
|
+
- False / 'none' - cache nothing
|
19
|
+
partial_mode: How to handle incomplete strings:
|
20
|
+
- False / 'off' - raise an exception if the input is incomplete
|
21
|
+
- True / 'on' - allow incomplete JSON but discard the last string if it is incomplete
|
22
|
+
- 'trailing-strings' - allow incomplete JSON, and include the last incomplete string in the output
|
23
|
+
catch_duplicate_keys: if True, raise an exception if objects contain the same key multiple times
|
24
|
+
lossless_floats: if True, preserve full detail on floats using `LosslessFloat`
|
25
|
+
"""
|
@@ -1,11 +1,16 @@
|
|
1
1
|
"""
|
2
|
-
|
3
|
-
|
2
|
+
loads(obj: str | bytes | bytearray | memoryview) -> ta.Any | oj.JSONDEcodeError
|
3
|
+
dumps(
|
4
|
+
obj: ta.Any,
|
5
|
+
default: ta.Callable[[ta.Any], Ata.ny] | None = ...,
|
6
|
+
option: int | None = ...,
|
7
|
+
) -> bytes
|
4
8
|
"""
|
5
9
|
import dataclasses as dc
|
6
10
|
import typing as ta
|
7
11
|
|
8
12
|
from .... import lang
|
13
|
+
from .base import Backend
|
9
14
|
|
10
15
|
|
11
16
|
if ta.TYPE_CHECKING:
|
@@ -14,6 +19,9 @@ else:
|
|
14
19
|
oj = lang.proxy_import('orjson')
|
15
20
|
|
16
21
|
|
22
|
+
##
|
23
|
+
|
24
|
+
|
17
25
|
@dc.dataclass(frozen=True, kw_only=True)
|
18
26
|
class Options:
|
19
27
|
append_newline: bool = False # append \n to the output
|
@@ -70,3 +78,39 @@ class Options:
|
|
70
78
|
class DumpOpts:
|
71
79
|
default: ta.Callable[[ta.Any], ta.Any] | None = None
|
72
80
|
option: Options = Options()
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
|
85
|
+
|
86
|
+
class OrjsonBackend(Backend):
|
87
|
+
def dump(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
88
|
+
fp.write(self.dumps(obj, **kwargs))
|
89
|
+
|
90
|
+
def dumps(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
91
|
+
return oj.dumps(obj, **kwargs).decode('utf-8')
|
92
|
+
|
93
|
+
def load(self, fp: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
94
|
+
return oj.loads(fp.read(), **kwargs)
|
95
|
+
|
96
|
+
def loads(self, s: str | bytes | bytearray, **kwargs: ta.Any) -> ta.Any:
|
97
|
+
return oj.loads(s, **kwargs)
|
98
|
+
|
99
|
+
def dump_pretty(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
100
|
+
fp.write(self.dumps_pretty(obj, **kwargs))
|
101
|
+
|
102
|
+
def dumps_pretty(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
103
|
+
return self.dumps(obj, option=kwargs.pop('option', 0) | oj.OPT_INDENT_2, **kwargs)
|
104
|
+
|
105
|
+
def dump_compact(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
106
|
+
return self.dump(obj, fp, **kwargs)
|
107
|
+
|
108
|
+
def dumps_compact(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
109
|
+
return self.dumps(obj, **kwargs)
|
110
|
+
|
111
|
+
|
112
|
+
ORJSON_BACKEND: OrjsonBackend | None
|
113
|
+
if lang.can_import('orjson'):
|
114
|
+
ORJSON_BACKEND = OrjsonBackend()
|
115
|
+
else:
|
116
|
+
ORJSON_BACKEND = None
|
@@ -8,6 +8,13 @@ import dataclasses as dc
|
|
8
8
|
import json
|
9
9
|
import typing as ta
|
10
10
|
|
11
|
+
from ..consts import COMPACT_KWARGS
|
12
|
+
from ..consts import PRETTY_KWARGS
|
13
|
+
from .base import Backend
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
11
18
|
|
12
19
|
@dc.dataclass(frozen=True, kw_only=True)
|
13
20
|
class DumpOpts:
|
@@ -35,3 +42,35 @@ class LoadOpts:
|
|
35
42
|
|
36
43
|
# called with the result of any object literal decoded with an ordered list of pairs, by default dict # noqa
|
37
44
|
object_pairs_hook: ta.Callable[[list[tuple[str, ta.Any]]], ta.Any] | None = None
|
45
|
+
|
46
|
+
|
47
|
+
##
|
48
|
+
|
49
|
+
|
50
|
+
class StdBackend(Backend):
|
51
|
+
def dump(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
52
|
+
json.dump(obj, fp, **kwargs)
|
53
|
+
|
54
|
+
def dumps(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
55
|
+
return json.dumps(obj, **kwargs)
|
56
|
+
|
57
|
+
def load(self, fp: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
58
|
+
return json.load(fp, **kwargs)
|
59
|
+
|
60
|
+
def loads(self, s: str | bytes | bytearray, **kwargs: ta.Any) -> ta.Any:
|
61
|
+
return json.loads(s, **kwargs)
|
62
|
+
|
63
|
+
def dump_pretty(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
64
|
+
json.dump(obj, fp, **PRETTY_KWARGS, **kwargs)
|
65
|
+
|
66
|
+
def dumps_pretty(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
67
|
+
return json.dumps(obj, **PRETTY_KWARGS, **kwargs)
|
68
|
+
|
69
|
+
def dump_compact(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
70
|
+
json.dump(obj, fp, **COMPACT_KWARGS, **kwargs)
|
71
|
+
|
72
|
+
def dumps_compact(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
73
|
+
return json.dumps(obj, **COMPACT_KWARGS, **kwargs)
|
74
|
+
|
75
|
+
|
76
|
+
STD_BACKEND = StdBackend()
|
@@ -7,6 +7,19 @@ dumps(obj: ta.Any, **DumpOpts) -> None
|
|
7
7
|
import dataclasses as dc
|
8
8
|
import typing as ta
|
9
9
|
|
10
|
+
from .... import lang
|
11
|
+
from ..consts import PRETTY_INDENT
|
12
|
+
from .base import Backend
|
13
|
+
|
14
|
+
|
15
|
+
if ta.TYPE_CHECKING:
|
16
|
+
import ujson as uj
|
17
|
+
else:
|
18
|
+
uj = lang.proxy_import('ujson')
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
10
23
|
|
11
24
|
@dc.dataclass(frozen=True, kw_only=True)
|
12
25
|
class DumpOpts:
|
@@ -23,3 +36,39 @@ class DumpOpts:
|
|
23
36
|
reject_bytes: bool = True
|
24
37
|
default: ta.Callable[[ta.Any], ta.Any] | None = None # should return a serializable version of obj or raise TypeError # noqa
|
25
38
|
separators: tuple[str, str] | None = None
|
39
|
+
|
40
|
+
|
41
|
+
##
|
42
|
+
|
43
|
+
|
44
|
+
class UjsonBackend(Backend):
|
45
|
+
def dump(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
46
|
+
uj.dump(obj, fp, **kwargs)
|
47
|
+
|
48
|
+
def dumps(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
49
|
+
return uj.dumps(obj, **kwargs)
|
50
|
+
|
51
|
+
def load(self, fp: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
52
|
+
return uj.load(fp, **kwargs)
|
53
|
+
|
54
|
+
def loads(self, s: str | bytes | bytearray, **kwargs: ta.Any) -> ta.Any:
|
55
|
+
return uj.loads(s, **kwargs)
|
56
|
+
|
57
|
+
def dump_pretty(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
58
|
+
uj.dump(obj, fp, indent=PRETTY_INDENT, **kwargs)
|
59
|
+
|
60
|
+
def dumps_pretty(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
61
|
+
return uj.dumps(obj, indent=PRETTY_INDENT, **kwargs)
|
62
|
+
|
63
|
+
def dump_compact(self, obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
64
|
+
uj.dump(obj, fp, **kwargs)
|
65
|
+
|
66
|
+
def dumps_compact(self, obj: ta.Any, **kwargs: ta.Any) -> str:
|
67
|
+
return uj.dumps(obj, **kwargs)
|
68
|
+
|
69
|
+
|
70
|
+
UJSON_BACKEND: UjsonBackend | None
|
71
|
+
if lang.can_import('ujson'):
|
72
|
+
UJSON_BACKEND = UjsonBackend()
|
73
|
+
else:
|
74
|
+
UJSON_BACKEND = None
|
File without changes
|
@@ -1,8 +1,6 @@
|
|
1
1
|
import argparse
|
2
2
|
import codecs
|
3
3
|
import contextlib
|
4
|
-
import dataclasses as dc
|
5
|
-
import enum
|
6
4
|
import io
|
7
5
|
import json
|
8
6
|
import os
|
@@ -10,31 +8,25 @@ import subprocess
|
|
10
8
|
import sys
|
11
9
|
import typing as ta
|
12
10
|
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from .
|
18
|
-
from .
|
11
|
+
from .... import check
|
12
|
+
from .... import lang
|
13
|
+
from .... import term
|
14
|
+
from ..render import JsonRenderer
|
15
|
+
from ..stream.build import JsonObjectBuilder
|
16
|
+
from ..stream.lex import JsonStreamLexer
|
17
|
+
from ..stream.parse import JsonStreamParser
|
18
|
+
from ..stream.render import StreamJsonRenderer
|
19
|
+
from .formats import FORMATS_BY_NAME
|
20
|
+
from .formats import Formats
|
19
21
|
|
20
22
|
|
21
23
|
if ta.TYPE_CHECKING:
|
22
|
-
import
|
23
|
-
import tomllib
|
24
|
-
|
25
|
-
import yaml
|
26
|
-
|
27
|
-
from .. import dotenv
|
28
|
-
from .. import props
|
29
|
-
|
24
|
+
from ....specs import jmespath
|
30
25
|
else:
|
31
|
-
|
32
|
-
tomllib = lang.proxy_import('tomllib')
|
26
|
+
jmespath = lang.proxy_import('....specs.jmespath', __package__)
|
33
27
|
|
34
|
-
yaml = lang.proxy_import('yaml')
|
35
28
|
|
36
|
-
|
37
|
-
props = lang.proxy_import('..props', __package__)
|
29
|
+
##
|
38
30
|
|
39
31
|
|
40
32
|
def term_color(o: ta.Any, state: JsonRenderer.State) -> tuple[str, str]:
|
@@ -46,39 +38,19 @@ def term_color(o: ta.Any, state: JsonRenderer.State) -> tuple[str, str]:
|
|
46
38
|
return '', ''
|
47
39
|
|
48
40
|
|
49
|
-
@dc.dataclass(frozen=True)
|
50
|
-
class Format:
|
51
|
-
names: ta.Sequence[str]
|
52
|
-
load: ta.Callable[[ta.TextIO], ta.Any]
|
53
|
-
|
54
|
-
|
55
|
-
class Formats(enum.Enum):
|
56
|
-
JSON = Format(['json'], json.load)
|
57
|
-
YAML = Format(['yaml', 'yml'], lambda f: yaml.safe_load(f))
|
58
|
-
TOML = Format(['toml'], lambda f: tomllib.loads(f.read()))
|
59
|
-
ENV = Format(['env', 'dotenv'], lambda f: dotenv.dotenv_values(stream=f))
|
60
|
-
PROPS = Format(['properties', 'props'], lambda f: dict(props.Properties().load(f.read())))
|
61
|
-
PY = Format(['py', 'python', 'repr'], lambda f: ast.literal_eval(f.read()))
|
62
|
-
|
63
|
-
|
64
|
-
FORMATS_BY_NAME: ta.Mapping[str, Format] = {
|
65
|
-
n: f
|
66
|
-
for e in Formats
|
67
|
-
for f in [e.value]
|
68
|
-
for n in f.names
|
69
|
-
}
|
70
|
-
|
71
|
-
|
72
41
|
def _main() -> None:
|
73
42
|
parser = argparse.ArgumentParser()
|
74
43
|
|
75
44
|
parser.add_argument('file', nargs='?')
|
76
45
|
|
77
46
|
parser.add_argument('--stream', action='store_true')
|
78
|
-
parser.add_argument('--stream-
|
47
|
+
parser.add_argument('--stream-build', action='store_true')
|
48
|
+
parser.add_argument('--stream-buffer-size', type=int, default=0x4000)
|
79
49
|
|
80
50
|
parser.add_argument('-f', '--format')
|
81
51
|
|
52
|
+
parser.add_argument('-x', '--jmespath-expr')
|
53
|
+
|
82
54
|
parser.add_argument('-z', '--compact', action='store_true')
|
83
55
|
parser.add_argument('-p', '--pretty', action='store_true')
|
84
56
|
parser.add_argument('-i', '--indent')
|
@@ -111,7 +83,15 @@ def _main() -> None:
|
|
111
83
|
sort_keys=args.sort_keys,
|
112
84
|
)
|
113
85
|
|
86
|
+
if args.jmespath_expr is not None:
|
87
|
+
jp_expr = jmespath.compile(args.jmespath_expr)
|
88
|
+
else:
|
89
|
+
jp_expr = None
|
90
|
+
|
114
91
|
def render_one(v: ta.Any) -> str:
|
92
|
+
if jp_expr is not None:
|
93
|
+
v = jp_expr.search(v)
|
94
|
+
|
115
95
|
if args.color:
|
116
96
|
return JsonRenderer.render_str(
|
117
97
|
v,
|
@@ -179,7 +159,21 @@ def _main() -> None:
|
|
179
159
|
|
180
160
|
with contextlib.ExitStack() as es2:
|
181
161
|
lex = es2.enter_context(JsonStreamLexer())
|
182
|
-
|
162
|
+
parse = es2.enter_context(JsonStreamParser())
|
163
|
+
|
164
|
+
if args.stream_build:
|
165
|
+
build = es2.enter_context(JsonObjectBuilder())
|
166
|
+
renderer = None
|
167
|
+
|
168
|
+
else:
|
169
|
+
renderer = StreamJsonRenderer(
|
170
|
+
out,
|
171
|
+
StreamJsonRenderer.Options(
|
172
|
+
**kw,
|
173
|
+
style=term_color if args.color else None,
|
174
|
+
),
|
175
|
+
)
|
176
|
+
build = None
|
183
177
|
|
184
178
|
while True:
|
185
179
|
buf = os.read(fd, args.stream_buffer_size)
|
@@ -188,8 +182,14 @@ def _main() -> None:
|
|
188
182
|
n = 0
|
189
183
|
for c in s:
|
190
184
|
for t in lex(c):
|
191
|
-
for
|
192
|
-
|
185
|
+
for e in parse(t):
|
186
|
+
if renderer is not None:
|
187
|
+
renderer.render((e,))
|
188
|
+
|
189
|
+
if build is not None:
|
190
|
+
for v in build(e):
|
191
|
+
print(render_one(v), file=out)
|
192
|
+
|
193
193
|
n += 1
|
194
194
|
|
195
195
|
if n:
|
@@ -198,6 +198,8 @@ def _main() -> None:
|
|
198
198
|
if not buf:
|
199
199
|
break
|
200
200
|
|
201
|
+
out.write('\n')
|
202
|
+
|
201
203
|
else:
|
202
204
|
with io.TextIOWrapper(in_file) as tw:
|
203
205
|
v = fmt.load(tw)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- options lol - csv header, newline, etc
|
4
|
+
"""
|
5
|
+
import dataclasses as dc
|
6
|
+
import enum
|
7
|
+
import json
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
from .... import lang
|
11
|
+
|
12
|
+
|
13
|
+
if ta.TYPE_CHECKING:
|
14
|
+
import ast
|
15
|
+
import csv
|
16
|
+
import tomllib
|
17
|
+
|
18
|
+
import yaml
|
19
|
+
|
20
|
+
from ... import dotenv
|
21
|
+
from ... import props
|
22
|
+
from ... import xml
|
23
|
+
|
24
|
+
else:
|
25
|
+
ast = lang.proxy_import('ast')
|
26
|
+
csv = lang.proxy_import('csv')
|
27
|
+
tomllib = lang.proxy_import('tomllib')
|
28
|
+
|
29
|
+
yaml = lang.proxy_import('yaml')
|
30
|
+
|
31
|
+
dotenv = lang.proxy_import('...dotenv', __package__)
|
32
|
+
props = lang.proxy_import('...props', __package__)
|
33
|
+
xml = lang.proxy_import('...xml', __package__)
|
34
|
+
|
35
|
+
|
36
|
+
##
|
37
|
+
|
38
|
+
|
39
|
+
@dc.dataclass(frozen=True)
|
40
|
+
class Format:
|
41
|
+
names: ta.Sequence[str]
|
42
|
+
load: ta.Callable[[ta.TextIO], ta.Any]
|
43
|
+
|
44
|
+
|
45
|
+
class Formats(enum.Enum):
|
46
|
+
JSON = Format(['json'], json.load)
|
47
|
+
YAML = Format(['yaml', 'yml'], lambda f: yaml.safe_load(f))
|
48
|
+
TOML = Format(['toml'], lambda f: tomllib.loads(f.read()))
|
49
|
+
ENV = Format(['env', 'dotenv'], lambda f: dotenv.dotenv_values(stream=f))
|
50
|
+
PROPS = Format(['properties', 'props'], lambda f: dict(props.Properties().load(f.read())))
|
51
|
+
PY = Format(['py', 'python', 'repr'], lambda f: ast.literal_eval(f.read()))
|
52
|
+
XML = Format(['xml'], lambda f: xml.build_simple_element(xml.parse_tree(f.read()).getroot()).as_dict())
|
53
|
+
CSV = Format(['csv'], lambda f: list(csv.DictReader(f)))
|
54
|
+
TSV = Format(['tsv'], lambda f: list(csv.DictReader(f, delimiter='\t')))
|
55
|
+
FLAT_CSV = Format(['fcsv'], lambda f: list(csv.reader(f)))
|
56
|
+
FLAT_TSV = Format(['ftsv'], lambda f: list(csv.reader(f, delimiter='\t')))
|
57
|
+
|
58
|
+
|
59
|
+
FORMATS_BY_NAME: ta.Mapping[str, Format] = {
|
60
|
+
n: f
|
61
|
+
for e in Formats
|
62
|
+
for f in [e.value]
|
63
|
+
for n in f.names
|
64
|
+
}
|