omlish 0.0.0.dev451__py3-none-any.whl → 0.0.0.dev453__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.
@@ -0,0 +1,213 @@
1
+ import typing as ta
2
+
3
+ from ... import check
4
+ from ... import collections as col
5
+ from ... import dataclasses as dc
6
+ from ... import lang
7
+ from ... import reflect as rfl
8
+ from ...funcs import match as mfs
9
+ from ..base.contexts import MarshalContext
10
+ from ..base.contexts import UnmarshalContext
11
+ from ..base.types import Marshaler
12
+ from ..base.types import Unmarshaler
13
+ from ..base.values import Value
14
+ from ..factories.simple import SimpleMarshalerFactory
15
+ from ..factories.simple import SimpleUnmarshalerFactory
16
+
17
+
18
+ ##
19
+
20
+
21
+ class MatchUnionMarshaler(Marshaler):
22
+ mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
23
+
24
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
25
+ try:
26
+ m = self.mmf.match(ctx, o)
27
+ except mfs.AmbiguousMatchesError:
28
+ raise ValueError(o) # noqa
29
+ return m.fn(ctx, o)
30
+
31
+
32
+ class MatchUnionUnmarshaler(Unmarshaler):
33
+ mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
34
+
35
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
36
+ try:
37
+ m = self.mmf.match(ctx, v)
38
+ except mfs.AmbiguousMatchesError:
39
+ raise ValueError(v) # noqa
40
+ return m.fn(ctx, v)
41
+
42
+
43
+ ##
44
+
45
+
46
+ PRIMITIVE_UNION_TYPES: tuple[type, ...] = (
47
+ float,
48
+ int,
49
+ str,
50
+ bool,
51
+ )
52
+
53
+
54
+ class DestructuredPrimitiveUnionType(ta.NamedTuple):
55
+ prim: ta.Sequence[type]
56
+ non_prim: ta.Sequence[rfl.Type]
57
+
58
+
59
+ def _destructure_primitive_union_type(
60
+ rty: rfl.Type,
61
+ prim_tys: ta.Container[type] = PRIMITIVE_UNION_TYPES,
62
+ ) -> DestructuredPrimitiveUnionType | None:
63
+ if not isinstance(rty, rfl.Union):
64
+ return None
65
+
66
+ pr = col.partition(rty.args, lambda a: isinstance(a, type) and a in prim_tys)
67
+ if not pr.t or len(pr.f) > 1:
68
+ return None
69
+
70
+ return DestructuredPrimitiveUnionType(*pr) # type: ignore[arg-type]
71
+
72
+
73
+ #
74
+
75
+
76
+ @dc.dataclass(frozen=True)
77
+ class PrimitiveUnionMarshaler(Marshaler):
78
+ tys: ta.Sequence[type]
79
+ x: Marshaler | None = None
80
+
81
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
82
+ if type(o) not in self.tys:
83
+ if self.x is not None:
84
+ return self.x.marshal(ctx, o)
85
+ raise TypeError(o)
86
+ return o
87
+
88
+
89
+ @dc.dataclass(frozen=True)
90
+ class PrimitiveUnionMarshalerFactory(SimpleMarshalerFactory):
91
+ tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
92
+
93
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
94
+ return _destructure_primitive_union_type(rty, self.tys) is not None
95
+
96
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
97
+ prim, non_prim = check.not_none(_destructure_primitive_union_type(rty, self.tys))
98
+ return PrimitiveUnionMarshaler(
99
+ prim,
100
+ ctx.make(check.single(non_prim)) if non_prim else None,
101
+ )
102
+
103
+
104
+ #
105
+
106
+
107
+ @dc.dataclass(frozen=True)
108
+ class PrimitiveUnionUnmarshaler(Unmarshaler):
109
+ tys: ta.Sequence[type]
110
+ x: Unmarshaler | None = None
111
+
112
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
113
+ if type(v) not in self.tys:
114
+ if self.x is not None:
115
+ return self.x.unmarshal(ctx, v)
116
+ raise TypeError(v)
117
+ return v
118
+
119
+
120
+ @dc.dataclass(frozen=True)
121
+ class PrimitiveUnionUnmarshalerFactory(SimpleUnmarshalerFactory):
122
+ tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
123
+
124
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
125
+ return _destructure_primitive_union_type(rty, self.tys) is not None
126
+
127
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
128
+ prim, non_prim = check.not_none(_destructure_primitive_union_type(rty, self.tys))
129
+ return PrimitiveUnionUnmarshaler(
130
+ prim,
131
+ ctx.make(check.single(non_prim)) if non_prim else None,
132
+ )
133
+
134
+
135
+ ##
136
+
137
+
138
+ LITERAL_UNION_TYPES: tuple[type, ...] = (
139
+ int,
140
+ str,
141
+ )
142
+
143
+
144
+ class DestructuredLiteralUnionType(ta.NamedTuple):
145
+ v_ty: type
146
+ lit: rfl.Literal
147
+ non_lit: rfl.Type
148
+
149
+
150
+ def _destructure_literal_union_type(rty: rfl.Type) -> DestructuredLiteralUnionType | None:
151
+ if not isinstance(rty, rfl.Union):
152
+ return None
153
+ lits, non_lits = col.partition(rty.args, lang.isinstance_of(rfl.Literal)) # noqa
154
+ if len(lits) != 1 or len(non_lits) != 1:
155
+ return None
156
+ lit = check.isinstance(check.single(lits), rfl.Literal)
157
+ v_tys = set(map(type, lit.args))
158
+ if len(v_tys) != 1:
159
+ return None
160
+ [v_ty] = v_tys
161
+ if v_ty in rty.args or v_ty not in LITERAL_UNION_TYPES:
162
+ return None
163
+ return DestructuredLiteralUnionType(v_ty, lit, check.single(non_lits))
164
+
165
+
166
+ #
167
+
168
+
169
+ @dc.dataclass(frozen=True)
170
+ class LiteralUnionMarshaler(Marshaler):
171
+ v_ty: type
172
+ l: Marshaler
173
+ x: Marshaler
174
+
175
+ def marshal(self, ctx: MarshalContext, o: ta.Any | None) -> Value:
176
+ if isinstance(o, self.v_ty):
177
+ return self.l.marshal(ctx, o)
178
+ else:
179
+ return self.x.marshal(ctx, o)
180
+
181
+
182
+ class LiteralUnionMarshalerFactory(SimpleMarshalerFactory):
183
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
184
+ return _destructure_literal_union_type(rty) is not None
185
+
186
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
187
+ ds = check.not_none(_destructure_literal_union_type(rty))
188
+ return LiteralUnionMarshaler(ds.v_ty, ctx.make(ds.lit), ctx.make(ds.non_lit))
189
+
190
+
191
+ #
192
+
193
+
194
+ @dc.dataclass(frozen=True)
195
+ class LiteralUnionUnmarshaler(Unmarshaler):
196
+ v_ty: type
197
+ l: Unmarshaler
198
+ x: Unmarshaler
199
+
200
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any | None:
201
+ if isinstance(v, self.v_ty):
202
+ return self.l.unmarshal(ctx, v) # type: ignore[arg-type]
203
+ else:
204
+ return self.x.unmarshal(ctx, v)
205
+
206
+
207
+ class LiteralUnionUnmarshalerFactory(SimpleUnmarshalerFactory):
208
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
209
+ return _destructure_literal_union_type(rty) is not None
210
+
211
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
212
+ ds = check.not_none(_destructure_literal_union_type(rty))
213
+ return LiteralUnionUnmarshaler(ds.v_ty, ctx.make(ds.lit), ctx.make(ds.non_lit))
@@ -43,11 +43,11 @@ class _ModuleImportingFactory(mfs.MatchFn[[ContextT, rfl.Type], R]):
43
43
  self._callback = callback
44
44
 
45
45
  self._lock = threading.RLock()
46
- self._has_imported = False
46
+ self._last_mis: ta.Any = None
47
47
 
48
- def _do_import(self, ctx: ContextT) -> None:
48
+ def _do_import(self, ctx: ContextT, mis: ta.Sequence[ModuleImport]) -> None:
49
49
  c = 0
50
- for mi in ctx.config_registry.get_of(None, ModuleImport):
50
+ for mi in mis:
51
51
  if mi.import_if_necessary():
52
52
  c += 1
53
53
 
@@ -56,13 +56,11 @@ class _ModuleImportingFactory(mfs.MatchFn[[ContextT, rfl.Type], R]):
56
56
  self._callback()
57
57
 
58
58
  def _import_if_necessary(self, ctx: ContextT) -> None:
59
- # FIXME:
60
- # if not self._has_imported:
61
- # with self._lock:
62
- # if not self._has_imported:
63
- # self._do_import(ctx)
64
- # self._has_imported = True
65
- self._do_import(ctx)
59
+ if (mis := ctx.config_registry.get_of(None, ModuleImport)) and mis is not self._last_mis:
60
+ with self._lock:
61
+ if (mis := ctx.config_registry.get_of(None, ModuleImport)) and mis is not self._last_mis:
62
+ self._do_import(ctx, mis)
63
+ self._last_mis = mis
66
64
 
67
65
  def guard(self, ctx: ContextT, rty: rfl.Type) -> bool:
68
66
  self._import_if_necessary(ctx)
@@ -107,23 +107,34 @@ class Polymorphism:
107
107
  return self._impls
108
108
 
109
109
 
110
+ class AutoStripSuffix(lang.Marker):
111
+ pass
112
+
113
+
110
114
  def polymorphism_from_impls(
111
115
  ty: type,
112
116
  impls: ta.Iterable[type],
113
117
  *,
114
118
  naming: Naming | None = None,
115
- strip_suffix: bool | ta.Literal['auto'] = False,
119
+ strip_suffix: bool | type[AutoStripSuffix] | str = False,
116
120
  ) -> Polymorphism:
117
121
  impls = set(impls)
118
122
 
119
- if strip_suffix == 'auto':
123
+ ssx: str | None
124
+ if strip_suffix is AutoStripSuffix:
120
125
  strip_suffix = all(c.__name__.endswith(ty.__name__) for c in impls)
126
+ if isinstance(strip_suffix, bool):
127
+ ssx = ty.__name__ if strip_suffix else None
128
+ elif isinstance(strip_suffix, str):
129
+ ssx = strip_suffix
130
+ else:
131
+ raise TypeError(strip_suffix)
121
132
 
122
133
  dct: dict[str, Impl] = {}
123
134
  for cur in impls:
124
135
  name = cur.__name__
125
- if strip_suffix:
126
- name = lang.strip_suffix(name, ty.__name__)
136
+ if ssx is not None:
137
+ name = lang.strip_suffix(name, ssx)
127
138
  if naming is not None:
128
139
  name = translate_name(name, naming)
129
140
  if name in dct:
@@ -141,7 +152,7 @@ def polymorphism_from_subclasses(
141
152
  ty: type,
142
153
  *,
143
154
  naming: Naming | None = None,
144
- strip_suffix: bool | ta.Literal['auto'] = False,
155
+ strip_suffix: bool | type[AutoStripSuffix] | str = False,
145
156
  ) -> Polymorphism:
146
157
  return polymorphism_from_impls(
147
158
  ty,
@@ -1,9 +1,13 @@
1
+ import typing as ta
2
+
1
3
  from ..base.types import MarshalerFactory
2
4
  from ..base.types import UnmarshalerFactory
3
5
  from .marshal import PolymorphismMarshalerFactory
4
6
  from .metadata import Polymorphism
5
7
  from .metadata import TypeTagging
6
8
  from .metadata import WrapperTypeTagging
9
+ from .unions import PolymorphismUnionMarshalerFactory
10
+ from .unions import PolymorphismUnionUnmarshalerFactory
7
11
  from .unmarshal import PolymorphismUnmarshalerFactory
8
12
 
9
13
 
@@ -13,8 +17,18 @@ from .unmarshal import PolymorphismUnmarshalerFactory
13
17
  def standard_polymorphism_factories(
14
18
  poly: Polymorphism,
15
19
  tt: TypeTagging = WrapperTypeTagging(),
16
- ) -> tuple[MarshalerFactory, UnmarshalerFactory]:
17
- return (
20
+ *,
21
+ unions: bool | ta.Literal['partial'] = False,
22
+ ) -> ta.Sequence[MarshalerFactory | UnmarshalerFactory]:
23
+ out: list[MarshalerFactory | UnmarshalerFactory] = [
18
24
  PolymorphismMarshalerFactory(poly, tt),
19
25
  PolymorphismUnmarshalerFactory(poly, tt),
20
- )
26
+ ]
27
+
28
+ if unions:
29
+ out.extend([
30
+ PolymorphismUnionMarshalerFactory(poly.impls, tt, allow_partial=unions == 'partial'),
31
+ PolymorphismUnionUnmarshalerFactory(poly.impls, tt, allow_partial=unions == 'partial'),
32
+ ])
33
+
34
+ return out
@@ -1,17 +1,12 @@
1
- import typing as ta
2
-
3
1
  from ... import cached
4
2
  from ... import check
5
- from ... import collections as col
6
3
  from ... import dataclasses as dc
7
4
  from ... import lang
8
5
  from ... import reflect as rfl
9
- from ...funcs import match as mfs
10
6
  from ..base.contexts import MarshalContext
11
7
  from ..base.contexts import UnmarshalContext
12
8
  from ..base.types import Marshaler
13
9
  from ..base.types import Unmarshaler
14
- from ..base.values import Value
15
10
  from ..factories.simple import SimpleMarshalerFactory
16
11
  from ..factories.simple import SimpleUnmarshalerFactory
17
12
  from .marshal import make_polymorphism_marshaler
@@ -24,130 +19,6 @@ from .unmarshal import make_polymorphism_unmarshaler
24
19
  ##
25
20
 
26
21
 
27
- class MatchUnionMarshaler(Marshaler):
28
- mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
29
-
30
- def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
31
- try:
32
- m = self.mmf.match(ctx, o)
33
- except mfs.AmbiguousMatchesError:
34
- raise ValueError(o) # noqa
35
- return m.fn(ctx, o)
36
-
37
-
38
- class MatchUnionUnmarshaler(Unmarshaler):
39
- mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
40
-
41
- def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
42
- try:
43
- m = self.mmf.match(ctx, v)
44
- except mfs.AmbiguousMatchesError:
45
- raise ValueError(v) # noqa
46
- return m.fn(ctx, v)
47
-
48
-
49
- ##
50
-
51
-
52
- PRIMITIVE_UNION_TYPES: tuple[type, ...] = (
53
- float,
54
- int,
55
- str,
56
- bool,
57
- )
58
-
59
-
60
- class DestructuredPrimitiveUnionType(ta.NamedTuple):
61
- prim: ta.Sequence[type]
62
- non_prim: ta.Sequence[rfl.Type]
63
-
64
-
65
- def _destructure_primitive_union_type(
66
- rty: rfl.Type,
67
- prim_tys: ta.Container[type] = PRIMITIVE_UNION_TYPES,
68
- ) -> DestructuredPrimitiveUnionType | None:
69
- if not isinstance(rty, rfl.Union):
70
- return None
71
-
72
- pr = col.partition(rty.args, lambda a: isinstance(a, type) and a in prim_tys)
73
- if not pr.t or len(pr.f) > 1:
74
- return None
75
-
76
- return DestructuredPrimitiveUnionType(*pr) # type: ignore[arg-type]
77
-
78
-
79
- #
80
-
81
-
82
- @dc.dataclass(frozen=True)
83
- class PrimitiveUnionMarshaler(Marshaler):
84
- tys: ta.Sequence[type]
85
- x: Marshaler | None = None
86
-
87
- def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
88
- if type(o) not in self.tys:
89
- if self.x is not None:
90
- return self.x.marshal(ctx, o)
91
- raise TypeError(o)
92
- return o
93
-
94
-
95
- @dc.dataclass(frozen=True)
96
- class PrimitiveUnionMarshalerFactory(SimpleMarshalerFactory):
97
- tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
98
-
99
- def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
100
- return _destructure_primitive_union_type(rty, self.tys) is not None
101
-
102
- def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
103
- prim, non_prim = check.not_none(_destructure_primitive_union_type(rty, self.tys))
104
- return PrimitiveUnionMarshaler(
105
- prim,
106
- ctx.make(check.single(non_prim)) if non_prim else None,
107
- )
108
-
109
-
110
- #
111
-
112
-
113
- @dc.dataclass(frozen=True)
114
- class PrimitiveUnionUnmarshaler(Unmarshaler):
115
- tys: ta.Sequence[type]
116
- x: Unmarshaler | None = None
117
-
118
- def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
119
- if type(v) not in self.tys:
120
- if self.x is not None:
121
- return self.x.unmarshal(ctx, v)
122
- raise TypeError(v)
123
- return v
124
-
125
-
126
- @dc.dataclass(frozen=True)
127
- class PrimitiveUnionUnmarshalerFactory(SimpleUnmarshalerFactory):
128
- tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
129
-
130
- def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
131
- return _destructure_primitive_union_type(rty, self.tys) is not None
132
-
133
- def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
134
- prim, non_prim = check.not_none(_destructure_primitive_union_type(rty, self.tys))
135
- return PrimitiveUnionUnmarshaler(
136
- prim,
137
- ctx.make(check.single(non_prim)) if non_prim else None,
138
- )
139
-
140
-
141
- #
142
-
143
-
144
- PRIMITIVE_UNION_MARSHALER_FACTORY = PrimitiveUnionMarshalerFactory()
145
- PRIMITIVE_UNION_UNMARSHALER_FACTORY = PrimitiveUnionUnmarshalerFactory()
146
-
147
-
148
- ##
149
-
150
-
151
22
  @dc.dataclass(frozen=True)
152
23
  class _BasePolymorphismUnionFactory(lang.Abstract):
153
24
  impls: Impls
@@ -22,6 +22,10 @@ from .composite.optionals import OptionalMarshalerFactory
22
22
  from .composite.optionals import OptionalUnmarshalerFactory
23
23
  from .composite.special import SequenceNotStrMarshalerFactory
24
24
  from .composite.special import SequenceNotStrUnmarshalerFactory
25
+ from .composite.unions import LiteralUnionMarshalerFactory
26
+ from .composite.unions import LiteralUnionUnmarshalerFactory
27
+ from .composite.unions import PrimitiveUnionMarshalerFactory
28
+ from .composite.unions import PrimitiveUnionUnmarshalerFactory
25
29
  from .factories.invalidate import InvalidatableMarshalerFactory
26
30
  from .factories.invalidate import InvalidatableUnmarshalerFactory
27
31
  from .factories.moduleimport.factories import ModuleImportingMarshalerFactory
@@ -36,8 +40,6 @@ from .objects.dataclasses import DataclassMarshalerFactory
36
40
  from .objects.dataclasses import DataclassUnmarshalerFactory
37
41
  from .objects.namedtuples import NamedtupleMarshalerFactory
38
42
  from .objects.namedtuples import NamedtupleUnmarshalerFactory
39
- from .polymorphism.unions import PrimitiveUnionMarshalerFactory
40
- from .polymorphism.unions import PrimitiveUnionUnmarshalerFactory
41
43
  from .singular.datetimes import DATETIME_MARSHALER_FACTORY
42
44
  from .singular.datetimes import DATETIME_UNMARSHALER_FACTORY
43
45
  from .singular.enums import EnumMarshalerFactory
@@ -65,6 +67,7 @@ DEFAULT_STANDARD_FACTORIES = StandardFactories(
65
67
  PRIMITIVE_MARSHALER_FACTORY,
66
68
  NewtypeMarshalerFactory(),
67
69
  OptionalMarshalerFactory(),
70
+ LiteralUnionMarshalerFactory(),
68
71
  PrimitiveUnionMarshalerFactory(),
69
72
  DataclassMarshalerFactory(),
70
73
  NamedtupleMarshalerFactory(),
@@ -84,6 +87,7 @@ DEFAULT_STANDARD_FACTORIES = StandardFactories(
84
87
  PRIMITIVE_UNMARSHALER_FACTORY,
85
88
  NewtypeUnmarshalerFactory(),
86
89
  OptionalUnmarshalerFactory(),
90
+ LiteralUnionUnmarshalerFactory(),
87
91
  PrimitiveUnionUnmarshalerFactory(),
88
92
  DataclassUnmarshalerFactory(),
89
93
  NamedtupleUnmarshalerFactory(),
@@ -92,7 +92,7 @@ def _install_standard_marshaling() -> None:
92
92
  p = msh.polymorphism_from_subclasses(
93
93
  cls,
94
94
  naming=msh.Naming.SNAKE,
95
- strip_suffix='auto',
95
+ strip_suffix=msh.AutoStripSuffix,
96
96
  )
97
97
  msh.install_standard_factories(
98
98
  msh.PolymorphismMarshalerFactory(p),
@@ -81,7 +81,7 @@ class Renderer(lang.Abstract):
81
81
  def args(self) -> lang.Args:
82
82
  return self._params_preparer.prepare()
83
83
 
84
- @dispatch.method
84
+ @dispatch.method(instance_cache=True)
85
85
  def render(self, o: ta.Any) -> tp.Part:
86
86
  raise TypeError(o)
87
87
 
omlish/text/parts.py CHANGED
@@ -75,7 +75,7 @@ class PartTransform:
75
75
  def __call__(self, part: Part | None) -> Part:
76
76
  return self._transform(part)
77
77
 
78
- @dispatch.method
78
+ @dispatch.method(instance_cache=True)
79
79
  def _transform(self, part: Part | None) -> Part:
80
80
  raise TypeError(part)
81
81
 
@@ -214,7 +214,7 @@ class PartRenderer:
214
214
  def __call__(self, part: Part | None) -> None:
215
215
  return self._render(part)
216
216
 
217
- @dispatch.method
217
+ @dispatch.method()
218
218
  def _render(self, part: Part | None) -> None:
219
219
  raise TypeError(part)
220
220
 
@@ -19,7 +19,7 @@ def _build_typed_value_poly(rty: rfl.Type) -> msh.Polymorphism:
19
19
  return msh.polymorphism_from_subclasses(
20
20
  ty,
21
21
  naming=msh.Naming.SNAKE,
22
- strip_suffix='auto',
22
+ strip_suffix=msh.AutoStripSuffix,
23
23
  )
24
24
 
25
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev451
3
+ Version: 0.0.0.dev453
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause