omlish 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev493__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/.omlish-manifests.json +12 -0
- omlish/README.md +199 -0
- omlish/__about__.py +21 -16
- omlish/argparse/all.py +17 -9
- omlish/argparse/cli.py +16 -3
- omlish/argparse/utils.py +21 -0
- omlish/asyncs/asyncio/rlock.py +110 -0
- omlish/asyncs/asyncio/sync.py +43 -0
- omlish/asyncs/asyncio/utils.py +2 -0
- omlish/asyncs/sync.py +25 -0
- omlish/bootstrap/_marshal.py +1 -1
- omlish/bootstrap/diag.py +12 -21
- omlish/bootstrap/main.py +2 -5
- omlish/bootstrap/sys.py +27 -28
- omlish/cexts/__init__.py +0 -0
- omlish/cexts/include/omlish/omlish.hh +1 -0
- omlish/collections/__init__.py +13 -1
- omlish/collections/attrregistry.py +210 -0
- omlish/collections/cache/impl.py +1 -0
- omlish/collections/identity.py +1 -0
- omlish/collections/mappings.py +28 -0
- omlish/collections/trie.py +5 -1
- omlish/collections/utils.py +77 -0
- omlish/concurrent/all.py +2 -1
- omlish/concurrent/futures.py +25 -0
- omlish/concurrent/threadlets.py +1 -1
- omlish/daemons/reparent.py +2 -3
- omlish/daemons/spawning.py +2 -3
- omlish/dataclasses/__init__.py +2 -0
- omlish/dataclasses/impl/api/classes/decorator.py +3 -0
- omlish/dataclasses/impl/api/classes/make.py +3 -0
- omlish/dataclasses/impl/concerns/repr.py +15 -2
- omlish/dataclasses/impl/configs.py +97 -37
- omlish/dataclasses/impl/generation/compilation.py +21 -19
- omlish/dataclasses/impl/generation/globals.py +1 -0
- omlish/dataclasses/impl/generation/ops.py +1 -0
- omlish/dataclasses/impl/generation/plans.py +2 -17
- omlish/dataclasses/impl/generation/processor.py +106 -25
- omlish/dataclasses/impl/processing/base.py +8 -0
- omlish/dataclasses/impl/processing/driving.py +19 -7
- omlish/dataclasses/specs.py +1 -0
- omlish/dataclasses/tools/modifiers.py +5 -0
- omlish/diag/_pycharm/runhack.py +1 -1
- omlish/diag/cmds/__init__.py +0 -0
- omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
- omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
- omlish/diag/{ps.py → cmds/ps.py} +6 -6
- omlish/diag/pycharm.py +16 -2
- omlish/diag/pydevd.py +58 -40
- omlish/diag/replserver/console.py +1 -1
- omlish/dispatch/__init__.py +18 -12
- omlish/dispatch/methods.py +50 -140
- omlish/dom/rendering.py +1 -1
- omlish/formats/dotenv.py +1 -1
- omlish/formats/json/stream/__init__.py +13 -0
- omlish/funcs/guard.py +225 -0
- omlish/graphs/dot/rendering.py +1 -1
- omlish/http/all.py +44 -4
- omlish/http/clients/asyncs.py +33 -27
- omlish/http/clients/base.py +17 -1
- omlish/http/clients/coro/__init__.py +0 -0
- omlish/http/clients/coro/sync.py +171 -0
- omlish/http/clients/default.py +208 -29
- omlish/http/clients/executor.py +56 -0
- omlish/http/clients/httpx.py +72 -11
- omlish/http/clients/middleware.py +181 -0
- omlish/http/clients/sync.py +33 -27
- omlish/http/clients/syncasync.py +49 -0
- omlish/http/clients/urllib.py +6 -3
- omlish/http/coro/client/connection.py +15 -6
- omlish/http/coro/io.py +2 -0
- omlish/http/flasky/__init__.py +40 -0
- omlish/http/flasky/_compat.py +2 -0
- omlish/http/flasky/api.py +82 -0
- omlish/http/flasky/app.py +203 -0
- omlish/http/flasky/cvs.py +59 -0
- omlish/http/flasky/requests.py +20 -0
- omlish/http/flasky/responses.py +23 -0
- omlish/http/flasky/routes.py +23 -0
- omlish/http/flasky/types.py +15 -0
- omlish/http/urls.py +67 -0
- omlish/inject/__init__.py +57 -29
- omlish/inject/_dataclasses.py +5148 -0
- omlish/inject/binder.py +11 -52
- omlish/inject/eagers.py +2 -0
- omlish/inject/elements.py +27 -0
- omlish/inject/helpers/__init__.py +0 -0
- omlish/inject/{utils.py → helpers/constfn.py} +3 -3
- omlish/inject/{tags.py → helpers/id.py} +2 -2
- omlish/inject/helpers/late.py +76 -0
- omlish/inject/{managed.py → helpers/managed.py} +10 -10
- omlish/inject/helpers/multis.py +143 -0
- omlish/inject/helpers/wrappers.py +54 -0
- omlish/inject/impl/elements.py +54 -21
- omlish/inject/impl/injector.py +29 -25
- omlish/inject/impl/inspect.py +10 -1
- omlish/inject/impl/maysync.py +3 -4
- omlish/inject/impl/multis.py +3 -0
- omlish/inject/impl/sync.py +3 -4
- omlish/inject/injector.py +31 -2
- omlish/inject/inspect.py +35 -0
- omlish/inject/maysync.py +2 -4
- omlish/inject/multis.py +8 -0
- omlish/inject/overrides.py +3 -3
- omlish/inject/privates.py +6 -0
- omlish/inject/providers.py +3 -2
- omlish/inject/sync.py +5 -4
- omlish/io/buffers.py +118 -0
- omlish/io/readers.py +29 -0
- omlish/iterators/transforms.py +2 -2
- omlish/lang/__init__.py +180 -97
- omlish/lang/_asyncs.cc +186 -0
- omlish/lang/asyncs.py +17 -0
- omlish/lang/casing.py +11 -0
- omlish/lang/contextmanagers.py +28 -4
- omlish/lang/functions.py +31 -22
- omlish/lang/imports/_capture.cc +11 -9
- omlish/lang/imports/capture.py +363 -170
- omlish/lang/imports/proxy.py +455 -152
- omlish/lang/lazyglobals.py +22 -9
- omlish/lang/params.py +17 -0
- omlish/lang/recursion.py +0 -1
- omlish/lang/sequences.py +124 -0
- omlish/lifecycles/README.md +30 -0
- omlish/lifecycles/__init__.py +87 -13
- omlish/lifecycles/_dataclasses.py +1388 -0
- omlish/lifecycles/base.py +178 -64
- omlish/lifecycles/contextmanagers.py +113 -4
- omlish/lifecycles/controller.py +150 -87
- omlish/lifecycles/injection.py +143 -0
- omlish/lifecycles/listeners.py +56 -0
- omlish/lifecycles/managed.py +142 -0
- omlish/lifecycles/manager.py +218 -93
- omlish/lifecycles/states.py +2 -0
- omlish/lifecycles/transitions.py +3 -0
- omlish/lifecycles/unwrap.py +57 -0
- omlish/lite/abstract.py +54 -24
- omlish/lite/asyncs.py +2 -2
- omlish/lite/attrops.py +2 -0
- omlish/lite/contextmanagers.py +4 -4
- omlish/lite/dataclasses.py +44 -0
- omlish/lite/maybes.py +8 -0
- omlish/lite/maysync.py +1 -5
- omlish/lite/pycharm.py +1 -1
- omlish/lite/typing.py +24 -0
- omlish/logs/_amalg.py +1 -1
- omlish/logs/all.py +25 -11
- omlish/logs/asyncs.py +73 -0
- omlish/logs/base.py +101 -12
- omlish/logs/contexts.py +4 -1
- omlish/logs/lists.py +125 -0
- omlish/logs/modules.py +19 -1
- omlish/logs/std/loggers.py +6 -1
- omlish/logs/std/noisy.py +11 -9
- omlish/logs/{standard.py → std/standard.py} +3 -4
- omlish/logs/utils.py +17 -2
- omlish/manifests/loading.py +2 -1
- omlish/marshal/__init__.py +33 -13
- omlish/marshal/_dataclasses.py +2774 -0
- omlish/marshal/base/configs.py +12 -0
- omlish/marshal/base/contexts.py +36 -21
- omlish/marshal/base/funcs.py +8 -11
- omlish/marshal/base/options.py +8 -0
- omlish/marshal/base/registries.py +146 -44
- omlish/marshal/base/types.py +40 -16
- omlish/marshal/composite/iterables.py +33 -20
- omlish/marshal/composite/literals.py +20 -18
- omlish/marshal/composite/mappings.py +36 -23
- omlish/marshal/composite/maybes.py +29 -19
- omlish/marshal/composite/newtypes.py +16 -16
- omlish/marshal/composite/optionals.py +14 -14
- omlish/marshal/composite/special.py +15 -15
- omlish/marshal/composite/unions/__init__.py +0 -0
- omlish/marshal/composite/unions/literals.py +93 -0
- omlish/marshal/composite/unions/primitives.py +103 -0
- omlish/marshal/factories/invalidate.py +18 -68
- omlish/marshal/factories/method.py +26 -0
- omlish/marshal/factories/moduleimport/factories.py +22 -65
- omlish/marshal/factories/multi.py +13 -25
- omlish/marshal/factories/recursive.py +42 -56
- omlish/marshal/factories/typecache.py +29 -74
- omlish/marshal/factories/typemap.py +42 -43
- omlish/marshal/objects/dataclasses.py +129 -106
- omlish/marshal/objects/marshal.py +18 -14
- omlish/marshal/objects/namedtuples.py +48 -42
- omlish/marshal/objects/unmarshal.py +19 -15
- omlish/marshal/polymorphism/marshal.py +9 -11
- omlish/marshal/polymorphism/metadata.py +16 -5
- omlish/marshal/polymorphism/standard.py +13 -1
- omlish/marshal/polymorphism/unions.py +15 -105
- omlish/marshal/polymorphism/unmarshal.py +9 -10
- omlish/marshal/singular/enums.py +14 -18
- omlish/marshal/standard.py +10 -6
- omlish/marshal/trivial/any.py +1 -1
- omlish/marshal/trivial/forbidden.py +21 -26
- omlish/metadata.py +23 -1
- omlish/os/forkhooks.py +4 -0
- omlish/os/pidfiles/pinning.py +2 -2
- omlish/reflect/__init__.py +43 -26
- omlish/reflect/ops.py +10 -1
- omlish/reflect/types.py +1 -0
- omlish/secrets/marshal.py +1 -1
- omlish/specs/jmespath/__init__.py +12 -3
- omlish/specs/jmespath/_dataclasses.py +2893 -0
- omlish/specs/jmespath/ast.py +1 -1
- omlish/specs/jsonrpc/__init__.py +13 -0
- omlish/specs/jsonrpc/_marshal.py +32 -23
- omlish/specs/jsonrpc/conns.py +10 -7
- omlish/specs/jsonschema/_marshal.py +1 -1
- omlish/specs/jsonschema/keywords/__init__.py +7 -0
- omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
- omlish/specs/openapi/_marshal.py +31 -22
- omlish/sql/__init__.py +24 -5
- omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
- omlish/sql/api/dbapi.py +1 -1
- omlish/sql/dbapi/__init__.py +15 -0
- omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
- omlish/sql/queries/__init__.py +3 -0
- omlish/sql/queries/_marshal.py +2 -2
- omlish/sql/queries/rendering.py +1 -1
- omlish/sql/tabledefs/_marshal.py +1 -1
- omlish/subprocesses/base.py +4 -0
- omlish/subprocesses/editor.py +1 -1
- omlish/sync.py +155 -21
- omlish/term/alt.py +60 -0
- omlish/term/confirm.py +8 -8
- omlish/term/pager.py +235 -0
- omlish/term/terminfo.py +935 -0
- omlish/term/termstate.py +67 -0
- omlish/term/vt100/terminal.py +0 -3
- omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
- omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
- omlish/testing/pytest/plugins/skips.py +2 -1
- omlish/testing/unittest/main.py +3 -3
- omlish/text/docwrap/__init__.py +3 -0
- omlish/text/docwrap/__main__.py +11 -0
- omlish/text/docwrap/api.py +83 -0
- omlish/text/docwrap/cli.py +91 -0
- omlish/text/docwrap/groups.py +84 -0
- omlish/text/docwrap/lists.py +167 -0
- omlish/text/docwrap/parts.py +146 -0
- omlish/text/docwrap/reflowing.py +106 -0
- omlish/text/docwrap/rendering.py +151 -0
- omlish/text/docwrap/utils.py +11 -0
- omlish/text/docwrap/wrapping.py +59 -0
- omlish/text/filecache.py +2 -2
- omlish/text/lorem.py +6 -0
- omlish/text/parts.py +2 -2
- omlish/text/textwrap.py +51 -0
- omlish/typedvalues/marshal.py +85 -59
- omlish/typedvalues/values.py +2 -1
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/METADATA +36 -32
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/RECORD +260 -199
- omlish/dataclasses/impl/generation/mangling.py +0 -18
- omlish/funcs/match.py +0 -227
- omlish/lifecycles/abstract.py +0 -86
- omlish/marshal/factories/match.py +0 -34
- omlish/marshal/factories/simple.py +0 -28
- /omlish/inject/{impl → helpers}/proxy.py +0 -0
- /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
- /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from ... import check
|
|
4
|
+
from ... import dataclasses as dc
|
|
5
|
+
from ... import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dc.dataclass(frozen=True)
|
|
12
|
+
@dc.extra_class_params(terse_repr=True)
|
|
13
|
+
class Part(lang.Abstract, lang.Sealed):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dc.dataclass(frozen=True)
|
|
18
|
+
@dc.extra_class_params(terse_repr=True)
|
|
19
|
+
class Text(Part, lang.Final):
|
|
20
|
+
s: str
|
|
21
|
+
|
|
22
|
+
@dc.init
|
|
23
|
+
def _check_s(self) -> None:
|
|
24
|
+
check.non_empty_str(self.s)
|
|
25
|
+
check.state(self.s == self.s.strip())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dc.dataclass(frozen=True)
|
|
29
|
+
@dc.extra_class_params(terse_repr=True)
|
|
30
|
+
class Blank(Part, lang.Final):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dc.dataclass(frozen=True)
|
|
35
|
+
@dc.extra_class_params(terse_repr=True)
|
|
36
|
+
class Indent(Part, lang.Final):
|
|
37
|
+
n: int = dc.xfield(validate=lambda n: n > 0)
|
|
38
|
+
p: ta.Union[Text, 'Block', 'List'] = dc.xfield(coerce=lambda p: check.isinstance(p, (Text, Block, List)))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dc.dataclass(frozen=True)
|
|
42
|
+
@dc.extra_class_params(terse_repr=True)
|
|
43
|
+
class Block(Part, lang.Final):
|
|
44
|
+
ps: ta.Sequence[Part]
|
|
45
|
+
|
|
46
|
+
@dc.init
|
|
47
|
+
def _check_ps(self) -> None:
|
|
48
|
+
check.state(len(self.ps) > 1)
|
|
49
|
+
for i, p in enumerate(self.ps):
|
|
50
|
+
check.isinstance(p, Part)
|
|
51
|
+
if i and isinstance(p, Block):
|
|
52
|
+
check.not_isinstance(self.ps[i - 1], Block)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dc.dataclass(frozen=True)
|
|
56
|
+
@dc.extra_class_params(terse_repr=True)
|
|
57
|
+
class List(Part, lang.Final):
|
|
58
|
+
d: str = dc.xfield(coerce=check.non_empty_str)
|
|
59
|
+
es: ta.Sequence[Part] = dc.xfield()
|
|
60
|
+
|
|
61
|
+
@dc.init
|
|
62
|
+
def _check_es(self) -> None:
|
|
63
|
+
check.not_empty(self.es)
|
|
64
|
+
for e in self.es:
|
|
65
|
+
check.isinstance(e, Part)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _squish(ps: ta.Sequence[Part]) -> ta.Sequence[Part]:
|
|
72
|
+
for p in ps:
|
|
73
|
+
check.isinstance(p, Part)
|
|
74
|
+
|
|
75
|
+
if len(ps) < 2:
|
|
76
|
+
return ps
|
|
77
|
+
|
|
78
|
+
while True:
|
|
79
|
+
if any(isinstance(p, Block) for p in ps):
|
|
80
|
+
ps = list(lang.flatmap(lambda p: p.ps if isinstance(p, Block) else [p], ps))
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
if any(
|
|
84
|
+
isinstance(ps[i], Indent) and
|
|
85
|
+
isinstance(ps[i + 1], Indent) and
|
|
86
|
+
ps[i].n == ps[i + 1].n # type: ignore[attr-defined]
|
|
87
|
+
for i in range(len(ps) - 1)
|
|
88
|
+
):
|
|
89
|
+
new: list[Part | tuple[int, list[Part]]] = []
|
|
90
|
+
for p in ps:
|
|
91
|
+
if isinstance(p, Indent):
|
|
92
|
+
if new and isinstance(y := new[-1], tuple) and p.n == y[0]:
|
|
93
|
+
y[1].append(p.p)
|
|
94
|
+
else:
|
|
95
|
+
new.append((p.n, [p.p]))
|
|
96
|
+
else:
|
|
97
|
+
new.append(p)
|
|
98
|
+
ps = [
|
|
99
|
+
Indent(x[0], blockify(*x[1])) if isinstance(x, tuple) else x # type: ignore[arg-type]
|
|
100
|
+
for x in new
|
|
101
|
+
]
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
return ps
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def blockify(*ps: Part) -> Part:
|
|
110
|
+
check.not_empty(ps)
|
|
111
|
+
ps = _squish(ps) # type: ignore[assignment]
|
|
112
|
+
if len(ps) == 1:
|
|
113
|
+
return ps[0]
|
|
114
|
+
return Block(ps)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def unblockify(p: Part) -> ta.Sequence[Part]:
|
|
118
|
+
if isinstance(p, Block):
|
|
119
|
+
return p.ps
|
|
120
|
+
else:
|
|
121
|
+
return [p]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
##
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def build_root(s: str) -> Part:
|
|
128
|
+
lst: list[Part] = []
|
|
129
|
+
|
|
130
|
+
for l in s.split('\n'):
|
|
131
|
+
if not (sl := l.strip()):
|
|
132
|
+
lst.append(Blank())
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
p: Part = Text(sl)
|
|
136
|
+
|
|
137
|
+
n = next((i for i, c in enumerate(l) if not c.isspace()), 0)
|
|
138
|
+
if n:
|
|
139
|
+
p = Indent(n, p) # type: ignore[arg-type]
|
|
140
|
+
|
|
141
|
+
lst.append(p)
|
|
142
|
+
|
|
143
|
+
if len(lst) == 1:
|
|
144
|
+
return lst[0]
|
|
145
|
+
else:
|
|
146
|
+
return Block(lst)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FIXME:
|
|
3
|
+
- use wrapping.py with all its todos
|
|
4
|
+
"""
|
|
5
|
+
import dataclasses as dc
|
|
6
|
+
import textwrap
|
|
7
|
+
import typing as ta
|
|
8
|
+
|
|
9
|
+
from ... import lang
|
|
10
|
+
from ..textwrap import TextwrapOpts
|
|
11
|
+
from .parts import Blank
|
|
12
|
+
from .parts import Block
|
|
13
|
+
from .parts import Indent
|
|
14
|
+
from .parts import List
|
|
15
|
+
from .parts import Part
|
|
16
|
+
from .parts import Text
|
|
17
|
+
from .parts import blockify
|
|
18
|
+
from .parts import unblockify
|
|
19
|
+
from .utils import all_same
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TextReflower(ta.Protocol):
|
|
26
|
+
def __call__(self, strs: ta.Sequence[str], current_indent: int) -> ta.Sequence[str]: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NopReflower:
|
|
30
|
+
def __call__(self, strs: ta.Sequence[str], current_indent: int = 0) -> ta.Sequence[str]:
|
|
31
|
+
return strs
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dc.dataclass(frozen=True)
|
|
35
|
+
class JoiningReflower:
|
|
36
|
+
sep: str = ' '
|
|
37
|
+
|
|
38
|
+
def __call__(self, strs: ta.Sequence[str], current_indent: int = 0) -> ta.Sequence[str]:
|
|
39
|
+
return [self.sep.join(strs)]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dc.dataclass(frozen=True)
|
|
43
|
+
class TextwrapReflower:
|
|
44
|
+
sep: str = ' '
|
|
45
|
+
opts: TextwrapOpts = TextwrapOpts()
|
|
46
|
+
|
|
47
|
+
def __call__(self, strs: ta.Sequence[str], current_indent: int = 0) -> ta.Sequence[str]:
|
|
48
|
+
return textwrap.wrap(
|
|
49
|
+
self.sep.join(strs),
|
|
50
|
+
**dc.asdict(dc.replace(
|
|
51
|
+
self.opts,
|
|
52
|
+
width=self.opts.width - current_indent,
|
|
53
|
+
)),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reflow_block_text(
|
|
61
|
+
root: Part,
|
|
62
|
+
reflower: TextReflower = JoiningReflower(),
|
|
63
|
+
) -> Part:
|
|
64
|
+
def rec(p: Part, ci: int) -> Part:
|
|
65
|
+
if isinstance(p, Blank):
|
|
66
|
+
return p
|
|
67
|
+
|
|
68
|
+
elif isinstance(p, Text):
|
|
69
|
+
if (rf := reflower([p.s], ci)) != [p.s]:
|
|
70
|
+
p = blockify(*map(Text, rf)) # noqa
|
|
71
|
+
return p
|
|
72
|
+
|
|
73
|
+
elif isinstance(p, Indent):
|
|
74
|
+
if (np := rec(p.p, ci + p.n)) is not p.p:
|
|
75
|
+
p = Indent(p.n, np) # type: ignore[arg-type]
|
|
76
|
+
return p
|
|
77
|
+
|
|
78
|
+
elif isinstance(p, List):
|
|
79
|
+
ne = [rec(e, ci + len(p.d) + 1) for e in p.es]
|
|
80
|
+
if not all_same(ne, p.es):
|
|
81
|
+
p = List(p.d, ne)
|
|
82
|
+
return p
|
|
83
|
+
|
|
84
|
+
elif not isinstance(p, Block):
|
|
85
|
+
raise TypeError(p)
|
|
86
|
+
|
|
87
|
+
ps = [rec(c, ci) for c in p.ps]
|
|
88
|
+
|
|
89
|
+
ps = list(unblockify(blockify(*ps)))
|
|
90
|
+
|
|
91
|
+
new: list[Part | list[str]] = []
|
|
92
|
+
for c in ps:
|
|
93
|
+
if isinstance(c, Text):
|
|
94
|
+
if new and isinstance(x := new[-1], list):
|
|
95
|
+
x.append(c.s)
|
|
96
|
+
else:
|
|
97
|
+
new.append([c.s])
|
|
98
|
+
else:
|
|
99
|
+
new.append(c)
|
|
100
|
+
|
|
101
|
+
return blockify(*lang.flatmap(
|
|
102
|
+
lambda x: map(Text, reflower(x, ci)) if isinstance(x, list) else [x], # noqa
|
|
103
|
+
new,
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
return rec(root, 0)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import io
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ... import check
|
|
6
|
+
from .parts import Blank
|
|
7
|
+
from .parts import Block
|
|
8
|
+
from .parts import Indent
|
|
9
|
+
from .parts import List
|
|
10
|
+
from .parts import Part
|
|
11
|
+
from .parts import Text
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@functools.lru_cache
|
|
18
|
+
def _indent_str(n: int) -> str:
|
|
19
|
+
return ' ' * n
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def render_to(root: Part, out: ta.Callable[[str], ta.Any]) -> None:
|
|
26
|
+
i_stk: list[int] = [0]
|
|
27
|
+
ci = 0
|
|
28
|
+
need_nl = False
|
|
29
|
+
|
|
30
|
+
def write(
|
|
31
|
+
s: str,
|
|
32
|
+
ti: int | None = None,
|
|
33
|
+
endl: bool = False,
|
|
34
|
+
) -> None:
|
|
35
|
+
check.not_in('\n', s)
|
|
36
|
+
|
|
37
|
+
nonlocal need_nl
|
|
38
|
+
nonlocal ci
|
|
39
|
+
if need_nl:
|
|
40
|
+
check.state(ci == 0)
|
|
41
|
+
out('\n')
|
|
42
|
+
need_nl = False
|
|
43
|
+
|
|
44
|
+
if ti is None:
|
|
45
|
+
ti = i_stk[-1]
|
|
46
|
+
if ci < ti:
|
|
47
|
+
out(_indent_str(ti - ci))
|
|
48
|
+
ci = ti
|
|
49
|
+
|
|
50
|
+
out(s)
|
|
51
|
+
|
|
52
|
+
if endl:
|
|
53
|
+
need_nl = True
|
|
54
|
+
ci = 0
|
|
55
|
+
|
|
56
|
+
def rec(p: Part) -> None:
|
|
57
|
+
nonlocal ci
|
|
58
|
+
|
|
59
|
+
if isinstance(p, Blank):
|
|
60
|
+
check.state(ci == 0)
|
|
61
|
+
out('\n')
|
|
62
|
+
|
|
63
|
+
elif isinstance(p, Text):
|
|
64
|
+
write(p.s, endl=True)
|
|
65
|
+
|
|
66
|
+
elif isinstance(p, Block):
|
|
67
|
+
for c in p.ps:
|
|
68
|
+
rec(c)
|
|
69
|
+
|
|
70
|
+
elif isinstance(p, Indent):
|
|
71
|
+
i_stk.append(i_stk[-1] + p.n)
|
|
72
|
+
rec(p.p)
|
|
73
|
+
i_stk.pop()
|
|
74
|
+
|
|
75
|
+
elif isinstance(p, List):
|
|
76
|
+
i_stk.append((li := i_stk[-1]) + len(p.d) + 1)
|
|
77
|
+
sd = p.d + ' '
|
|
78
|
+
for e in p.es:
|
|
79
|
+
if isinstance(e, Blank):
|
|
80
|
+
rec(e)
|
|
81
|
+
else:
|
|
82
|
+
check.state(ci == 0)
|
|
83
|
+
write(sd, li)
|
|
84
|
+
ci += len(sd)
|
|
85
|
+
rec(e)
|
|
86
|
+
i_stk.pop()
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
raise TypeError(p)
|
|
90
|
+
|
|
91
|
+
rec(root)
|
|
92
|
+
check.state(i_stk == [0])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def render(root: Part) -> str:
|
|
96
|
+
buf = io.StringIO()
|
|
97
|
+
render_to(root, buf.write)
|
|
98
|
+
return buf.getvalue()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def dump_to(root: Part, out: ta.Callable[[str], ta.Any]) -> None:
|
|
105
|
+
i = 0
|
|
106
|
+
|
|
107
|
+
def write(s: str) -> None:
|
|
108
|
+
out(_indent_str(i * 2))
|
|
109
|
+
out(s)
|
|
110
|
+
out('\n')
|
|
111
|
+
|
|
112
|
+
def rec(p: Part) -> None:
|
|
113
|
+
nonlocal i
|
|
114
|
+
|
|
115
|
+
if isinstance(p, Blank):
|
|
116
|
+
write('blank')
|
|
117
|
+
|
|
118
|
+
elif isinstance(p, Text):
|
|
119
|
+
write(f'text:{p.s!r}')
|
|
120
|
+
|
|
121
|
+
elif isinstance(p, Block):
|
|
122
|
+
write(f'block/{len(p.ps)}')
|
|
123
|
+
i += 1
|
|
124
|
+
for c in p.ps:
|
|
125
|
+
rec(c)
|
|
126
|
+
i -= 1
|
|
127
|
+
|
|
128
|
+
elif isinstance(p, Indent):
|
|
129
|
+
write(f'indent:{p.n}')
|
|
130
|
+
i += 1
|
|
131
|
+
rec(p.p)
|
|
132
|
+
i -= 1
|
|
133
|
+
|
|
134
|
+
elif isinstance(p, List):
|
|
135
|
+
write(f'list/{len(p.es)}:{p.d!r}')
|
|
136
|
+
i += 1
|
|
137
|
+
for e in p.es:
|
|
138
|
+
rec(e)
|
|
139
|
+
i -= 1
|
|
140
|
+
|
|
141
|
+
else:
|
|
142
|
+
raise TypeError(p)
|
|
143
|
+
|
|
144
|
+
rec(root)
|
|
145
|
+
check.state(i == 0)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def dump(root: Part) -> str:
|
|
149
|
+
buf = io.StringIO()
|
|
150
|
+
dump_to(root, buf.write)
|
|
151
|
+
return buf.getvalue()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- a reflowing.TextReflower
|
|
4
|
+
- import textwrap lol
|
|
5
|
+
- obviously handle hyphens, underscores, etc
|
|
6
|
+
- optionally preserve/normalize inter-sentence spaces - '. ' vs '. '
|
|
7
|
+
- detect if 'intentionally' smaller than current remaining line width, if so do not merge.
|
|
8
|
+
- maybe if only ending with punctuation?
|
|
9
|
+
- detect 'matched pairs' of 'quotes'? to preserve whitespace? `foo bar` ...
|
|
10
|
+
- how to escape it lol - if we see any \\` do we give up?
|
|
11
|
+
"""
|
|
12
|
+
import re
|
|
13
|
+
import typing as ta
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SpanKind: ta.TypeAlias = ta.Literal[
|
|
17
|
+
'word',
|
|
18
|
+
'space',
|
|
19
|
+
'symbol',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Span(ta.NamedTuple):
|
|
27
|
+
k: SpanKind
|
|
28
|
+
s: str
|
|
29
|
+
|
|
30
|
+
def __repr__(self) -> str:
|
|
31
|
+
return f'{self.k}:{self.s!r}'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_SPAN_PAT = re.compile(r'(\w+)|(\s+)')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def split_line_spans(s: str) -> list[Span]:
|
|
38
|
+
spans: list[Span] = []
|
|
39
|
+
p = 0
|
|
40
|
+
for m in _SPAN_PAT.finditer(s):
|
|
41
|
+
l, r = m.span()
|
|
42
|
+
if p < l:
|
|
43
|
+
spans.append(Span('symbol', s[p:l]))
|
|
44
|
+
if m.group(1):
|
|
45
|
+
spans.append(Span('word', m.group(1)))
|
|
46
|
+
else:
|
|
47
|
+
spans.append(Span('space', m.group(2)))
|
|
48
|
+
p = r
|
|
49
|
+
if p < len(s):
|
|
50
|
+
spans.append(Span('symbol', s[p:]))
|
|
51
|
+
return spans
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _main() -> None:
|
|
55
|
+
print(split_line_spans('hi i am a string! this has a hy-phen.'))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == '__main__':
|
|
59
|
+
_main()
|
omlish/text/filecache.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Features:
|
|
3
3
|
- get (line, offset)
|
|
4
4
|
- invalidate
|
|
5
|
-
- filewatcher
|
|
5
|
+
- TODO: filewatcher
|
|
6
6
|
- linecache interop
|
|
7
7
|
- tokenize.open - detect encoding - only for .py
|
|
8
|
-
- directory / path_parts tree?
|
|
8
|
+
- TODO: directory / path_parts tree?
|
|
9
9
|
|
|
10
10
|
TODO:
|
|
11
11
|
- read raw, decode (detect)
|
omlish/text/lorem.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
LOREM = (
|
|
2
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore '
|
|
3
|
+
'magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo '
|
|
4
|
+
'consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. '
|
|
5
|
+
'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
|
6
|
+
)
|
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
|
|
omlish/text/textwrap.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dc.dataclass(frozen=True)
|
|
8
|
+
class TextwrapOpts:
|
|
9
|
+
# The maximum width of wrapped lines (unless break_long_words is false).
|
|
10
|
+
width: int = 70 # noqa
|
|
11
|
+
|
|
12
|
+
_: dc.KW_ONLY
|
|
13
|
+
|
|
14
|
+
# String that will be prepended to the first line of wrapped output. Counts towards the line's width.
|
|
15
|
+
initial_indent: str = ''
|
|
16
|
+
|
|
17
|
+
# String that will be prepended to all lines save the first of wrapped output; also counts towards each line's
|
|
18
|
+
# width.
|
|
19
|
+
subsequent_indent: str = ''
|
|
20
|
+
|
|
21
|
+
# Expand tabs in input text to spaces before further processing. Each tab will become 0 .. 'tabsize' spaces,
|
|
22
|
+
# depending on its position in its line. If false, each tab is treated as a single character.
|
|
23
|
+
expand_tabs: bool = True
|
|
24
|
+
|
|
25
|
+
# Expand tabs in input text to 0 .. 'tabsize' spaces, unless 'expand_tabs' is false.
|
|
26
|
+
tabsize: int = 8
|
|
27
|
+
|
|
28
|
+
# Replace all whitespace characters in the input text by spaces after tab expansion. Note that if expand_tabs is
|
|
29
|
+
# false and replace_whitespace is true, every tab will be converted to a single space!
|
|
30
|
+
replace_whitespace: bool = True
|
|
31
|
+
|
|
32
|
+
# Ensure that sentence-ending punctuation is always followed by two spaces. Off by default because the algorithm is
|
|
33
|
+
# (unavoidably) imperfect.
|
|
34
|
+
fix_sentence_endings: bool = False
|
|
35
|
+
|
|
36
|
+
# Break words longer than 'width'. If false, those words will not be broken, and some lines might be longer than
|
|
37
|
+
# 'width'.
|
|
38
|
+
break_long_words: bool = True
|
|
39
|
+
|
|
40
|
+
# Allow breaking hyphenated words. If true, wrapping will occur preferably on whitespaces and right after hyphens
|
|
41
|
+
# part of compound words.
|
|
42
|
+
break_on_hyphens: bool = True
|
|
43
|
+
|
|
44
|
+
# Drop leading and trailing whitespace from lines.
|
|
45
|
+
drop_whitespace: bool = True
|
|
46
|
+
|
|
47
|
+
# Truncate wrapped lines.
|
|
48
|
+
max_lines: int | None = None
|
|
49
|
+
|
|
50
|
+
# Append to the last line of truncated text.
|
|
51
|
+
placeholder: str = ' [...]'
|