omlish 0.0.0.dev473__py3-none-any.whl → 0.0.0.dev474__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/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev473'
2
- __revision__ = '9f130fc70f42b7e2117fc6530397d1bd295b37e6'
1
+ __version__ = '0.0.0.dev474'
2
+ __revision__ = '5a7b02bc5884a6c8e41f2e2afd5aaf7f495cc28e'
3
3
 
4
4
 
5
5
  #
@@ -41,7 +41,7 @@ class Project(ProjectBase):
41
41
 
42
42
  'greenlet ~= 3.2',
43
43
 
44
- 'trio ~= 0.31',
44
+ 'trio ~= 0.32',
45
45
  'trio-asyncio ~= 0.15',
46
46
  ],
47
47
 
File without changes
@@ -7,12 +7,12 @@ import dataclasses as dc
7
7
  import json
8
8
  import typing as ta
9
9
 
10
- from ..lite.check import check
11
- from ..lite.marshal import OBJ_MARSHALER_FIELD_KEY
12
- from ..lite.marshal import unmarshal_obj
13
- from ..subprocesses.run import SubprocessRun
14
- from ..subprocesses.run import SubprocessRunnable
15
- from ..subprocesses.run import SubprocessRunOutput
10
+ from ...lite.check import check
11
+ from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
12
+ from ...lite.marshal import unmarshal_obj
13
+ from ...subprocesses.run import SubprocessRun
14
+ from ...subprocesses.run import SubprocessRunnable
15
+ from ...subprocesses.run import SubprocessRunOutput
16
16
 
17
17
 
18
18
  ##
@@ -7,12 +7,12 @@ import dataclasses as dc
7
7
  import enum
8
8
  import typing as ta
9
9
 
10
- from ..lite.check import check
11
- from ..lite.dataclasses import dataclass_repr_omit_falsey
12
- from ..lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
13
- from ..subprocesses.run import SubprocessRun
14
- from ..subprocesses.run import SubprocessRunnable
15
- from ..subprocesses.run import SubprocessRunOutput
10
+ from ...lite.check import check
11
+ from ...lite.dataclasses import dataclass_repr_omit_falsey
12
+ from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
13
+ from ...subprocesses.run import SubprocessRun
14
+ from ...subprocesses.run import SubprocessRunnable
15
+ from ...subprocesses.run import SubprocessRunOutput
16
16
 
17
17
 
18
18
  ##
@@ -4,12 +4,12 @@ import dataclasses as dc
4
4
  import os
5
5
  import typing as ta
6
6
 
7
- from ..lite.check import check
8
- from ..lite.timeouts import Timeout
9
- from ..subprocesses.run import SubprocessRun
10
- from ..subprocesses.run import SubprocessRunnable
11
- from ..subprocesses.run import SubprocessRunOutput
12
- from ..subprocesses.sync import subprocesses
7
+ from ...lite.check import check
8
+ from ...lite.timeouts import Timeout
9
+ from ...subprocesses.run import SubprocessRun
10
+ from ...subprocesses.run import SubprocessRunnable
11
+ from ...subprocesses.run import SubprocessRunOutput
12
+ from ...subprocesses.sync import subprocesses
13
13
 
14
14
 
15
15
  ##
@@ -157,7 +157,7 @@ class CoroHttpClient(HttpClient):
157
157
  else:
158
158
  raise TypeError(o)
159
159
 
160
- def read1(self, /, n: int = -1) -> bytes:
160
+ def read1(self, n: int = -1, /) -> bytes:
161
161
  return self._run_coro(check.not_none(self._resp).read(n if n >= 0 else None))
162
162
 
163
163
  def close(self) -> None:
@@ -83,7 +83,7 @@ def client() -> HttpClient:
83
83
 
84
84
 
85
85
  @contextlib.contextmanager
86
- def manage_client(client: HttpClient | None) -> ta.Generator[HttpClient]: # noqa
86
+ def manage_client(client: HttpClient | None = None) -> ta.Generator[HttpClient]: # noqa
87
87
  if client is not None:
88
88
  yield client
89
89
 
@@ -167,7 +167,7 @@ def async_client() -> AsyncHttpClient:
167
167
 
168
168
 
169
169
  @contextlib.asynccontextmanager
170
- async def manage_async_client(client: AsyncHttpClient | None) -> ta.AsyncGenerator[AsyncHttpClient]: # noqa
170
+ async def manage_async_client(client: AsyncHttpClient | None = None) -> ta.AsyncGenerator[AsyncHttpClient]: # noqa
171
171
  if client is not None:
172
172
  yield client
173
173
 
@@ -30,10 +30,10 @@ class ExecutorAsyncHttpClient(AsyncHttpClient):
30
30
  owner: 'ExecutorAsyncHttpClient'
31
31
  resp: StreamHttpResponse
32
32
 
33
- async def read1(self, /, n: int = -1) -> bytes:
33
+ async def read1(self, n: int = -1, /) -> bytes:
34
34
  return await self.owner._run_in_executor(self.resp.stream.read1, n) # noqa
35
35
 
36
- async def read(self, /, n: int = -1) -> bytes:
36
+ async def read(self, n: int = -1, /) -> bytes:
37
37
  return await self.owner._run_in_executor(self.resp.stream.read, n) # noqa
38
38
 
39
39
  async def readall(self) -> bytes:
@@ -33,7 +33,7 @@ class HttpxHttpClient(HttpClient):
33
33
  class _StreamAdapter:
34
34
  it: ta.Iterator[bytes]
35
35
 
36
- def read1(self, /, n: int = -1) -> bytes:
36
+ def read1(self, n: int = -1, /) -> bytes:
37
37
  try:
38
38
  return next(self.it)
39
39
  except StopIteration:
@@ -82,7 +82,7 @@ class HttpxAsyncHttpClient(AsyncHttpClient):
82
82
  class _StreamAdapter:
83
83
  it: ta.AsyncIterator[bytes]
84
84
 
85
- async def read1(self, /, n: int = -1) -> bytes:
85
+ async def read1(self, n: int = -1, /) -> bytes:
86
86
  try:
87
87
  return await anext(self.it)
88
88
  except StopAsyncIteration:
@@ -105,27 +105,15 @@ class HttpxAsyncHttpClient(AsyncHttpClient):
105
105
  it = resp.aiter_bytes()
106
106
 
107
107
  # FIXME:
108
- # - https://github.com/encode/httpx/discussions/2963
109
- # - Exception ignored in: <async_generator object HTTP11ConnectionByteStream.__aiter__ at 0x1325a7540>
110
- # - RuntimeError: async generator ignored GeneratorExit
111
- # - Traced to:
112
- # - HTTP11ConnectionByteStream.aclose -> await self._connection._response_closed()
113
- # - AsyncHTTP11Connection._response_closed -> async with self._state_lock
114
- # - anyio._backends._asyncio.Lock.acquire -> await AsyncIOBackend.cancel_shielded_checkpoint() -> await sleep(0) # noqa
115
- # - Might have something to do with pycharm/pydevd's nested asyncio, doesn't seem to happen under trio ever
116
- # or asyncio outside debugger.
117
- @es.push_async_callback
118
- async def close_it() -> None:
119
- try:
120
- # print(f'close_it.begin: {it=}', file=sys.stderr)
121
- await it.aclose() # type: ignore[attr-defined]
122
- # print(f'close_it.end: {it=}', file=sys.stderr)
123
- except BaseException as be: # noqa
124
- # print(f'close_it.__exit__: {it=} {be=}', file=sys.stderr)
125
- raise
126
- finally:
127
- # print(f'close_it.finally: {it=}', file=sys.stderr)
128
- pass
108
+ # this has a tendency to raise `RuntimeError: async generator ignored GeneratorExit` when all of the
109
+ # following conditions are met:
110
+ # - stopped iterating midway through
111
+ # - shutting down the event loop
112
+ # - debugging under pycharm / pydevd
113
+ # - running under asyncio
114
+ # it does not seem to happen unless all of these conditions are met. see:
115
+ # https://gist.github.com/wrmsr/a0578ee5d5371b53804cfb56aeb84cdf .
116
+ es.push_async_callback(it.aclose) # type: ignore[attr-defined]
129
117
 
130
118
  return AsyncStreamHttpResponse(
131
119
  status=resp.status_code,
@@ -23,10 +23,10 @@ class SyncAsyncHttpClient(AsyncHttpClient):
23
23
  class _StreamAdapter:
24
24
  ul: StreamHttpResponse
25
25
 
26
- async def read1(self, /, n: int = -1) -> bytes:
26
+ async def read1(self, n: int = -1, /) -> bytes:
27
27
  return self.ul.stream.read1(n)
28
28
 
29
- async def read(self, /, n: int = -1) -> bytes:
29
+ async def read(self, n: int = -1, /) -> bytes:
30
30
  return self.ul.stream.read(n)
31
31
 
32
32
  async def readall(self) -> bytes:
omlish/io/buffers.py CHANGED
@@ -277,7 +277,7 @@ class ReadableListBuffer:
277
277
  self._buf = buf
278
278
  self._chunk_size = chunk_size or ReadableListBuffer.DEFAULT_BUFFERED_READER_CHUNK_SIZE
279
279
 
280
- def read1(self, /, n: int = -1) -> bytes:
280
+ def read1(self, n: int = -1, /) -> bytes:
281
281
  if n < 0:
282
282
  n = self._chunk_size
283
283
  if not n:
@@ -327,7 +327,7 @@ class ReadableListBuffer:
327
327
  self._buf = buf
328
328
  self._chunk_size = chunk_size or ReadableListBuffer.DEFAULT_BUFFERED_READER_CHUNK_SIZE
329
329
 
330
- async def read1(self, /, n: int = -1) -> bytes:
330
+ async def read1(self, n: int = -1, /) -> bytes:
331
331
  if n < 0:
332
332
  n = self._chunk_size
333
333
  if not n:
omlish/io/readers.py CHANGED
@@ -7,11 +7,11 @@ import typing as ta
7
7
 
8
8
 
9
9
  class RawBytesReader(ta.Protocol):
10
- def read1(self, /, n: int = -1) -> bytes: ...
10
+ def read1(self, n: int = -1, /) -> bytes: ...
11
11
 
12
12
 
13
13
  class BufferedBytesReader(RawBytesReader, ta.Protocol):
14
- def read(self, /, n: int = -1) -> bytes: ...
14
+ def read(self, n: int = -1, /) -> bytes: ...
15
15
 
16
16
  def readall(self) -> bytes: ...
17
17
 
@@ -20,10 +20,10 @@ class BufferedBytesReader(RawBytesReader, ta.Protocol):
20
20
 
21
21
 
22
22
  class AsyncRawBytesReader(ta.Protocol):
23
- def read1(self, /, n: int = -1) -> ta.Awaitable[bytes]: ...
23
+ def read1(self, n: int = -1, /) -> ta.Awaitable[bytes]: ...
24
24
 
25
25
 
26
26
  class AsyncBufferedBytesReader(AsyncRawBytesReader, ta.Protocol):
27
- def read(self, /, n: int = -1) -> ta.Awaitable[bytes]: ...
27
+ def read(self, n: int = -1, /) -> ta.Awaitable[bytes]: ...
28
28
 
29
29
  def readall(self) -> ta.Awaitable[bytes]: ...
@@ -25,8 +25,8 @@ import sys
25
25
  import time
26
26
  import typing as ta
27
27
 
28
- from ...diag.lslocks import LslocksCommand
29
- from ...diag.lsof import LsofCommand
28
+ from ...diag.cmds.lslocks import LslocksCommand
29
+ from ...diag.cmds.lsof import LsofCommand
30
30
  from ...lite.abstract import Abstract
31
31
  from ...lite.check import check
32
32
  from ...lite.timeouts import Timeout
@@ -0,0 +1,3 @@
1
+ from .api import ( # noqa
2
+ docwrap,
3
+ )
@@ -0,0 +1,77 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..textwrap import TextwrapOpts
5
+ from .groups import group_indents
6
+ from .lists import ListBuilder
7
+ from .parts import Part
8
+ from .parts import build_root
9
+ from .reflowing import TextwrapReflower
10
+ from .reflowing import reflow_block_text
11
+
12
+
13
+ ##
14
+
15
+
16
+ DEFAULT_TAB_WIDTH: int = 4
17
+
18
+
19
+ def replace_tabs(s: str, tab_width: int | None = None) -> str:
20
+ if tab_width is None:
21
+ tab_width = DEFAULT_TAB_WIDTH
22
+ return s.replace('\t', ' ' * tab_width)
23
+
24
+
25
+ ##
26
+
27
+
28
+ def parse(
29
+ s: str,
30
+ *,
31
+ tab_width: int | None = None,
32
+ allow_improper_list_children: bool | ta.Literal['lists_only'] | None = None,
33
+ ) -> Part:
34
+ s = replace_tabs(
35
+ s,
36
+ tab_width=tab_width,
37
+ )
38
+
39
+ root = build_root(s)
40
+
41
+ root = group_indents(root)
42
+
43
+ root = ListBuilder(
44
+ allow_improper_children=allow_improper_list_children,
45
+ ).build_lists(root)
46
+
47
+ return root
48
+
49
+
50
+ ##
51
+
52
+
53
+ def docwrap(
54
+ s: str,
55
+ *,
56
+ width: int | None = None,
57
+ textwrap: TextwrapOpts | ta.Mapping[str, ta.Any] | None = None,
58
+ allow_improper_list_children: bool | ta.Literal['lists_only'] = False,
59
+ ) -> Part:
60
+ if isinstance(textwrap, ta.Mapping):
61
+ textwrap = TextwrapOpts(**textwrap)
62
+ elif textwrap is None:
63
+ textwrap = TextwrapOpts()
64
+ if width is not None:
65
+ textwrap = dc.replace(textwrap, width=width)
66
+
67
+ root = parse(
68
+ s,
69
+ allow_improper_list_children=allow_improper_list_children,
70
+ )
71
+
72
+ root = reflow_block_text(
73
+ root,
74
+ TextwrapReflower(opts=textwrap),
75
+ )
76
+
77
+ return root
@@ -0,0 +1,84 @@
1
+ import typing as ta
2
+
3
+ from ... import check
4
+ from ... import dataclasses as dc
5
+ from .parts import Blank
6
+ from .parts import Block
7
+ from .parts import Indent
8
+ from .parts import Part
9
+ from .parts import Text
10
+ from .parts import blockify
11
+
12
+
13
+ ##
14
+
15
+
16
+ @dc.dataclass()
17
+ @dc.extra_class_params(default_repr_fn=dc.truthy_repr)
18
+ class _IndentGroup:
19
+ n: int
20
+ cs: list[ta.Union[Blank, Text, '_IndentGroup']] = dc.field(default_factory=list)
21
+
22
+
23
+ def group_indents(root: Part) -> Part:
24
+ rg = _IndentGroup(0)
25
+ stk: list[_IndentGroup] = [rg]
26
+
27
+ for p in (root.ps if isinstance(root, Block) else [root]):
28
+ if isinstance(p, Blank):
29
+ stk[-1].cs.append(p)
30
+ continue
31
+
32
+ n: int
33
+ t: Text
34
+ if isinstance(p, Text):
35
+ n, t = 0, p
36
+ elif isinstance(p, Indent):
37
+ n = p.n
38
+ t = check.isinstance(p.p, Text)
39
+ else:
40
+ raise TypeError(p)
41
+
42
+ while n < stk[-1].n:
43
+ stk.pop()
44
+
45
+ if n > stk[-1].n:
46
+ nxt = _IndentGroup(n=n, cs=[t])
47
+ stk[-1].cs.append(nxt)
48
+ stk.append(nxt)
49
+
50
+ else:
51
+ check.state(stk[-1].n == n)
52
+ stk[-1].cs.append(t)
53
+
54
+ #
55
+
56
+ def relativize(g: '_IndentGroup') -> None:
57
+ for c in g.cs:
58
+ if isinstance(c, _IndentGroup):
59
+ check.state(c.n > g.n)
60
+ relativize(c)
61
+ c.n -= g.n
62
+
63
+ relativize(rg)
64
+
65
+ #
66
+
67
+ def convert(g: '_IndentGroup') -> Part:
68
+ if g.n < 1:
69
+ check.state(g is rg)
70
+
71
+ lst: list[Part] = []
72
+ for c in g.cs:
73
+ if isinstance(c, (Blank, Text)):
74
+ lst.append(c)
75
+
76
+ elif isinstance(c, _IndentGroup):
77
+ lst.append(Indent(c.n, convert(c))) # type: ignore[arg-type]
78
+
79
+ else:
80
+ raise TypeError(c)
81
+
82
+ return blockify(*lst)
83
+
84
+ return convert(rg)
@@ -0,0 +1,167 @@
1
+ """
2
+ TODO:
3
+ - numeric lettered lists (even unordered) (with separator - `1)` / `1:` / ...)
4
+ """
5
+ import typing as ta
6
+
7
+ from ... import check
8
+ from .parts import Blank
9
+ from .parts import Block
10
+ from .parts import Indent
11
+ from .parts import List
12
+ from .parts import Part
13
+ from .parts import Text
14
+ from .parts import blockify
15
+ from .utils import all_same
16
+
17
+
18
+ ##
19
+
20
+
21
+ class ListBuilder:
22
+ DEFAULT_ALLOW_IMPROPER_CHILDREN: ta.ClassVar[bool | ta.Literal['lists_only']] = False
23
+ DEFAULT_LIST_PREFIXES: ta.ClassVar[ta.Sequence[str]] = ['*', '-']
24
+
25
+ def __init__(
26
+ self,
27
+ *,
28
+ list_prefixes: ta.Iterable[str] | None = None,
29
+ allow_improper_children: bool | ta.Literal['lists_only'] | None = None,
30
+ ) -> None:
31
+ super().__init__()
32
+
33
+ if list_prefixes is None:
34
+ list_prefixes = self.DEFAULT_LIST_PREFIXES
35
+ self._list_prefixes = set(check.not_isinstance(list_prefixes, str))
36
+ if allow_improper_children is None:
37
+ allow_improper_children = self.DEFAULT_ALLOW_IMPROPER_CHILDREN
38
+ self._allow_improper_children = allow_improper_children
39
+
40
+ self._len_sorted_list_prefixes = sorted(self._list_prefixes, key=len, reverse=True)
41
+
42
+ #
43
+
44
+ def _should_promote_indent_child(self, p: Indent) -> bool:
45
+ ac = self._allow_improper_children
46
+ if isinstance(ac, bool):
47
+ return ac
48
+ elif ac == 'lists_only':
49
+ return isinstance(p.p, List)
50
+ else:
51
+ raise TypeError(ac)
52
+
53
+ #
54
+
55
+ class _DetectedList(ta.NamedTuple):
56
+ pfx: str
57
+ ofs: int
58
+ len: int
59
+
60
+ def _detect_list(self, ps: ta.Sequence[Part], st: int = 0) -> _DetectedList | None:
61
+ if not ps:
62
+ return None
63
+
64
+ for lp in self._len_sorted_list_prefixes:
65
+ sp = lp + ' '
66
+
67
+ mo = -1
68
+ n = st
69
+ while n < len(ps):
70
+ p = ps[n]
71
+
72
+ if isinstance(p, (Blank, Text)):
73
+ if isinstance(p, Text):
74
+ if p.s.startswith(sp):
75
+ if mo < 0:
76
+ mo = n
77
+ elif mo >= 0:
78
+ break
79
+
80
+ elif isinstance(p, Indent):
81
+ if mo >= 0 and p.n < len(sp):
82
+ if not self._should_promote_indent_child(p):
83
+ break
84
+
85
+ elif isinstance(p, List):
86
+ if mo >= 0:
87
+ break
88
+
89
+ else:
90
+ raise TypeError(p)
91
+
92
+ n += 1
93
+
94
+ if mo >= 0:
95
+ return ListBuilder._DetectedList(lp, mo, n - mo)
96
+
97
+ return None
98
+
99
+ def _build_list(self, lp: str, ps: ta.Sequence[Part]) -> List:
100
+ sp = lp + ' '
101
+
102
+ new: list[list[Part]] = []
103
+
104
+ f = check.isinstance(ps[0], Text)
105
+ check.state(f.s.startswith(sp))
106
+ new.append([Text(f.s[len(sp):])])
107
+ del f
108
+
109
+ for i in range(1, len(ps)):
110
+ p = ps[i]
111
+
112
+ if isinstance(p, Blank):
113
+ new[-1].append(p)
114
+
115
+ elif isinstance(p, Text):
116
+ check.state(p.s.startswith(sp))
117
+ new.append([Text(p.s[len(sp):])])
118
+
119
+ elif isinstance(p, Indent):
120
+ if p.n < len(sp):
121
+ check.state(self._should_promote_indent_child(p))
122
+ p = Indent(len(sp), p.p)
123
+
124
+ if p.n == len(sp):
125
+ new[-1].append(p.p)
126
+
127
+ else:
128
+ raise NotImplementedError
129
+
130
+ else:
131
+ raise TypeError(p)
132
+
133
+ #
134
+
135
+ return List(lp, [blockify(*x) for x in new])
136
+
137
+ def build_lists(self, root: Part) -> Part:
138
+ def rec(p: Part) -> Part: # noqa
139
+ if isinstance(p, Block):
140
+ new = [rec(c) for c in p.ps]
141
+ if not all_same(new, p.ps):
142
+ return rec(blockify(*new))
143
+
144
+ st = 0
145
+ diff = False
146
+ while (dl := self._detect_list(new, st)) is not None:
147
+ diff = True
148
+ ln = self._build_list(dl.pfx, new[dl.ofs:dl.ofs + dl.len])
149
+ new[dl.ofs:dl.ofs + dl.len] = [ln]
150
+ st = dl.ofs + 1
151
+
152
+ if diff:
153
+ p = blockify(*new)
154
+ return p
155
+
156
+ elif isinstance(p, Indent):
157
+ if (n := rec(p.p)) is not p.p:
158
+ p = Indent(p.n, n) # type: ignore[arg-type]
159
+ return p
160
+
161
+ elif isinstance(p, (Blank, Text, List)):
162
+ return p
163
+
164
+ else:
165
+ raise TypeError(p)
166
+
167
+ return rec(root)
@@ -0,0 +1,139 @@
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
+ ##
118
+
119
+
120
+ def build_root(s: str) -> Part:
121
+ lst: list[Part] = []
122
+
123
+ for l in s.splitlines():
124
+ if not (sl := l.strip()):
125
+ lst.append(Blank())
126
+ continue
127
+
128
+ p: Part = Text(sl)
129
+
130
+ n = next((i for i, c in enumerate(l) if not c.isspace()), 0)
131
+ if n:
132
+ p = Indent(n, p) # type: ignore[arg-type]
133
+
134
+ lst.append(p)
135
+
136
+ if len(lst) == 1:
137
+ return lst[0]
138
+ else:
139
+ return Block(lst)
@@ -0,0 +1,103 @@
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 .utils import all_same
19
+
20
+
21
+ ##
22
+
23
+
24
+ class TextReflower(ta.Protocol):
25
+ def __call__(self, strs: ta.Sequence[str], current_indent: int) -> ta.Sequence[str]: ...
26
+
27
+
28
+ class NopReflower:
29
+ def __call__(self, strs: ta.Sequence[str], current_indent: int = 0) -> ta.Sequence[str]:
30
+ return strs
31
+
32
+
33
+ @dc.dataclass(frozen=True)
34
+ class JoiningReflower:
35
+ sep: str = ' '
36
+
37
+ def __call__(self, strs: ta.Sequence[str], current_indent: int = 0) -> ta.Sequence[str]:
38
+ return [self.sep.join(strs)]
39
+
40
+
41
+ @dc.dataclass(frozen=True)
42
+ class TextwrapReflower:
43
+ sep: str = ' '
44
+ opts: TextwrapOpts = TextwrapOpts()
45
+
46
+ def __call__(self, strs: ta.Sequence[str], current_indent: int = 0) -> ta.Sequence[str]:
47
+ return textwrap.wrap(
48
+ self.sep.join(strs),
49
+ **dc.asdict(dc.replace(
50
+ self.opts,
51
+ width=self.opts.width - current_indent,
52
+ )),
53
+ )
54
+
55
+
56
+ ##
57
+
58
+
59
+ def reflow_block_text(
60
+ root: Part,
61
+ reflower: TextReflower = JoiningReflower(),
62
+ ) -> Part:
63
+ def rec(p: Part, ci: int) -> Part:
64
+ if isinstance(p, Blank):
65
+ return p
66
+
67
+ elif isinstance(p, Text):
68
+ if (rf := reflower([p.s], ci)) != [p.s]:
69
+ p = blockify(*map(Text, rf)) # noqa
70
+ return p
71
+
72
+ elif isinstance(p, Indent):
73
+ if (np := rec(p.p, ci + p.n)) is not p.p:
74
+ p = Indent(p.n, np) # type: ignore[arg-type]
75
+ return p
76
+
77
+ elif isinstance(p, List):
78
+ ne = [rec(e, ci + len(p.d) + 1) for e in p.es]
79
+ if not all_same(ne, p.es):
80
+ p = List(p.d, ne)
81
+ return p
82
+
83
+ elif not isinstance(p, Block):
84
+ raise TypeError(p)
85
+
86
+ ps = [rec(c, ci) for c in p.ps]
87
+
88
+ new: list[Part | list[str]] = []
89
+ for c in ps:
90
+ if isinstance(c, Text):
91
+ if new and isinstance(x := new[-1], list):
92
+ x.append(c.s)
93
+ else:
94
+ new.append([c.s])
95
+ else:
96
+ new.append(c)
97
+
98
+ return blockify(*lang.flatmap(
99
+ lambda x: map(Text, reflower(x, ci)) if isinstance(x, list) else [x], # noqa
100
+ new,
101
+ ))
102
+
103
+ return rec(root, 0)
@@ -0,0 +1,142 @@
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
+
29
+ def write(s: str, ti: int | None = None) -> None:
30
+ if (nl := '\n' in s) and s != '\n':
31
+ check.state(s[-1] == '\n')
32
+ check.state(s.count('\n') == 1)
33
+
34
+ if ti is None:
35
+ ti = i_stk[-1]
36
+ nonlocal ci
37
+ if ci < ti:
38
+ out(_indent_str(ti - ci))
39
+ ci = ti
40
+
41
+ out(s)
42
+
43
+ if nl:
44
+ ci = 0
45
+
46
+ def rec(p: Part) -> None:
47
+ nonlocal ci
48
+
49
+ if isinstance(p, Blank):
50
+ check.state(ci == 0)
51
+ out('\n')
52
+
53
+ elif isinstance(p, Text):
54
+ write(p.s)
55
+ write('\n')
56
+
57
+ elif isinstance(p, Block):
58
+ for c in p.ps:
59
+ rec(c)
60
+
61
+ elif isinstance(p, Indent):
62
+ i_stk.append(i_stk[-1] + p.n)
63
+ rec(p.p)
64
+ i_stk.pop()
65
+
66
+ elif isinstance(p, List):
67
+ i_stk.append((li := i_stk[-1]) + len(p.d) + 1)
68
+ sd = p.d + ' '
69
+ for e in p.es:
70
+ if isinstance(e, Blank):
71
+ rec(e)
72
+ else:
73
+ check.state(ci == 0)
74
+ write(sd, li)
75
+ ci += len(sd)
76
+ rec(e)
77
+ i_stk.pop()
78
+
79
+ else:
80
+ raise TypeError(p)
81
+
82
+ rec(root)
83
+ check.state(i_stk == [0])
84
+
85
+
86
+ def render(root: Part) -> str:
87
+ buf = io.StringIO()
88
+ render_to(root, buf.write)
89
+ return buf.getvalue()
90
+
91
+
92
+ ##
93
+
94
+
95
+ def dump_to(root: Part, out: ta.Callable[[str], ta.Any]) -> None:
96
+ i = 0
97
+
98
+ def write(s: str) -> None:
99
+ out(_indent_str(i * 2))
100
+ out(s)
101
+ out('\n')
102
+
103
+ def rec(p: Part) -> None:
104
+ nonlocal i
105
+
106
+ if isinstance(p, Blank):
107
+ write('blank')
108
+
109
+ elif isinstance(p, Text):
110
+ write(f'text:{p.s!r}')
111
+
112
+ elif isinstance(p, Block):
113
+ write(f'block/{len(p.ps)}')
114
+ i += 1
115
+ for c in p.ps:
116
+ rec(c)
117
+ i -= 1
118
+
119
+ elif isinstance(p, Indent):
120
+ write(f'indent:{p.n}')
121
+ i += 1
122
+ rec(p.p)
123
+ i -= 1
124
+
125
+ elif isinstance(p, List):
126
+ write(f'list/{len(p.es)}:{p.d!r}')
127
+ i += 1
128
+ for e in p.es:
129
+ rec(e)
130
+ i -= 1
131
+
132
+ else:
133
+ raise TypeError(p)
134
+
135
+ rec(root)
136
+ check.state(i == 0)
137
+
138
+
139
+ def dump(root: Part) -> str:
140
+ buf = io.StringIO()
141
+ dump_to(root, buf.write)
142
+ return buf.getvalue()
@@ -0,0 +1,11 @@
1
+ import typing as ta
2
+
3
+
4
+ T = ta.TypeVar('T')
5
+
6
+
7
+ ##
8
+
9
+
10
+ def all_same(l: ta.Sequence[T], r: ta.Sequence[T]) -> bool:
11
+ return len(l) == len(r) and all(x is y for x, y in zip(l, r, strict=True))
@@ -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()
@@ -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 = ' [...]'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev473
3
+ Version: 0.0.0.dev474
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -18,7 +18,7 @@ Provides-Extra: all
18
18
  Requires-Dist: anyio~=4.11; extra == "all"
19
19
  Requires-Dist: sniffio~=1.3; extra == "all"
20
20
  Requires-Dist: greenlet~=3.2; extra == "all"
21
- Requires-Dist: trio~=0.31; extra == "all"
21
+ Requires-Dist: trio~=0.32; extra == "all"
22
22
  Requires-Dist: trio-asyncio~=0.15; extra == "all"
23
23
  Requires-Dist: lz4~=4.4; extra == "all"
24
24
  Requires-Dist: python-snappy~=0.7; extra == "all"
@@ -58,7 +58,7 @@ Provides-Extra: async
58
58
  Requires-Dist: anyio~=4.11; extra == "async"
59
59
  Requires-Dist: sniffio~=1.3; extra == "async"
60
60
  Requires-Dist: greenlet~=3.2; extra == "async"
61
- Requires-Dist: trio~=0.31; extra == "async"
61
+ Requires-Dist: trio~=0.32; extra == "async"
62
62
  Requires-Dist: trio-asyncio~=0.15; extra == "async"
63
63
  Provides-Extra: compress
64
64
  Requires-Dist: lz4~=4.4; extra == "compress"
@@ -234,7 +234,8 @@ dependencies of any kind**.
234
234
  - **[queries](https://github.com/wrmsr/omlish/blob/master/omlish/sql/queries)** - A SQL query builder with a fluent
235
235
  interface.
236
236
  - **[alchemy](https://github.com/wrmsr/omlish/blob/master/omlish/sql/alchemy)** - SQLAlchemy utilities. The codebase
237
- is moving away from SQLAlchemy however in favor of its own internal SQL api.
237
+ has moved away from SQLAlchemy in favor of its own internal SQL api, but it will likely still remain as an optional
238
+ dep for the api adapter.
238
239
 
239
240
  - **[testing](https://github.com/wrmsr/omlish/blob/master/omlish/testing)** - Test - primarily pytest - helpers,
240
241
  including:
@@ -294,8 +295,8 @@ examples are:
294
295
  - **pytest** - What is used for all standard testing - as lite code has no dependencies of any kind its testing uses
295
296
  stdlib's [unittest](https://docs.python.org/3/library/unittest.html).
296
297
  - **wrapt** - For (optionally-enabled) injector circular proxies.
297
- - **sqlalchemy** - Parts of the codebase use SQLAlchemy for db stuff, but it is being migrated away from in favor of the
298
- internal api. It will however likely still remain as an optional dep for the api adapter.
298
+ - **sqlalchemy** - The codebase has migrated away from SQLAlchemy in favor of the internal api but it retains it as an
299
+ optional dep to support adapting the internal api to it.
299
300
 
300
301
  Additionally, some catchall dep categories include:
301
302
 
@@ -1,5 +1,5 @@
1
1
  omlish/.omlish-manifests.json,sha256=FLw7xkPiSXuImZgqSP8BwrEib2R1doSzUPLUkc-QUIA,8410
2
- omlish/__about__.py,sha256=BoG13E5l8VG76ZQEVeoy6T90tE-JO2_DCUi_285ZWU0,3611
2
+ omlish/__about__.py,sha256=35_ul-npqd8WV5dBJiHcOowqXVDo-ayDrD_0zvxyImg,3611
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ZNIMl1kwg3qdei4DiUrJPQe5M81S1e76N-GuNSwLBAE,8683
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -202,17 +202,18 @@ omlish/dataclasses/tools/static.py,sha256=byDnPc0W15s3YPQNC9oyCASKVNxCytrHGLvDky
202
202
  omlish/diag/__init__.py,sha256=c1q8vuapGH1YiYdU300FIJXMI1MOcnLNBZXr-zc8BXk,1181
203
203
  omlish/diag/asts.py,sha256=MWh9XAG3m9L10FIJCyoNT2aU4Eft6tun_x9K0riq6Dk,3332
204
204
  omlish/diag/debug.py,sha256=ClED7kKXeVMyKrjGIxcq14kXk9kvUJfytBQwK9y7c4Q,1637
205
- omlish/diag/lslocks.py,sha256=VuA4MNNqXTcnHWsJvulGM6pNJRKmlXFXUZTfXpY0V6g,1750
206
- omlish/diag/lsof.py,sha256=5N5aZQ7UqEBgV-hj3_a8QcvALOeLlVb8otqF2hvucxY,9107
207
205
  omlish/diag/procfs.py,sha256=eeB3L9UpNBpAfsax3U6OczayYboPlFzOGplqlQ4gBNY,9700
208
206
  omlish/diag/procstats.py,sha256=EJEe2Zc58ykBoTfqMXro7H52aQa_pd6uC2hsIPFceso,825
209
- omlish/diag/ps.py,sha256=MEpMU6fbkh0bSWrOHh_okOa0JDTUSUQUVSYBdh1TGvE,1672
210
207
  omlish/diag/pycharm.py,sha256=_WVmPm1E66cBtR4ukgUAaApe_3rX9Cv3sQRP5PL37P8,5013
211
208
  omlish/diag/pydevd.py,sha256=JEHC0hiSJxL-vVtuxKUi1BNkgUKnRC1N4alDg6JP5WQ,8363
212
209
  omlish/diag/threads.py,sha256=sjtlTl41wxssoVCDkBB6xeLF-9kJEK3eA6hmSFWJSQA,3643
213
210
  omlish/diag/timers.py,sha256=cxX3GgjTIjBx9DI4pzCCO5Hfqb1TM3uo22yim7kjfRU,3831
214
211
  omlish/diag/_pycharm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
215
212
  omlish/diag/_pycharm/runhack.py,sha256=IxawPyqrPfY1fpV1D_9dZXxny89fVSR3RlmqlbaRctw,37869
213
+ omlish/diag/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
214
+ omlish/diag/cmds/lslocks.py,sha256=iEYe3OqcbMCKg1NbtO2EsYsnr2GnDYnNdi6Cbb_zEKM,1756
215
+ omlish/diag/cmds/lsof.py,sha256=hmrEZJNY0rQxa42t2Y3e3w5ZVe8rJqO2eI7_8HmzZEQ,9113
216
+ omlish/diag/cmds/ps.py,sha256=Ff0JusuXhJqpZ4w5cpOwovoRftQUtaKjT5YTtHfazl4,1678
216
217
  omlish/diag/replserver/__init__.py,sha256=uLo6V2aQ29v9z3IMELlPDSlG3_2iOT4-_X8VniF-EgE,235
217
218
  omlish/diag/replserver/__main__.py,sha256=LmU41lQ58bm1h4Mx7S8zhE_uEBSC6kPcp9mn5JRpulA,32
218
219
  omlish/diag/replserver/console.py,sha256=MxDLslAaYfC-l0rfGO-SnYAJ__8RWBJQ5FxGi29czYU,7438
@@ -325,15 +326,15 @@ omlish/http/wsgi.py,sha256=1JpfrY2JrQ0wrEVE0oLdQMWZw8Zcx0b4_9f3VmH4JKA,1070
325
326
  omlish/http/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
326
327
  omlish/http/clients/asyncs.py,sha256=QrbavgXv4BuQFW0bRojy-dxa_jpxjPVLA7eyfo1zzns,4230
327
328
  omlish/http/clients/base.py,sha256=bm3Oy2wuunSO3Rd2dxCmhHoZcQ8X9B5pkzaudbjZKvI,2810
328
- omlish/http/clients/default.py,sha256=fnz-pvPKHc7Kgft4XRwOZ6ZUUtt5D6ljuvOGAA8mgQw,5639
329
- omlish/http/clients/executor.py,sha256=yvjLs8XfJuHx0mDOC1nqKsQbQXqo2cldH8oQIBhfjOY,1854
330
- omlish/http/clients/httpx.py,sha256=Yp0u_DAPImqJ1_jlAN9RKWbkUP6Y_lfGJWJjaFWBdJQ,4793
329
+ omlish/http/clients/default.py,sha256=yQ7kOxlU-Dds8NdIJszeHkgnxtiJd6W-d0oUhKXTaYI,5653
330
+ omlish/http/clients/executor.py,sha256=r66xoGWdRsaSpfGtx8ovf3pOMG3k4UONlZEQZ58KAS0,1854
331
+ omlish/http/clients/httpx.py,sha256=T1Pa9Of6GylTmt9AGp8D6Uni3HxOtP7OPsImo35Kc1o,4029
331
332
  omlish/http/clients/middleware.py,sha256=xnXlNN1MU4sUKtkhPzdpRQsRh9wpLf1r7OGKjqGzLWg,5058
332
333
  omlish/http/clients/sync.py,sha256=jVbqbUlruuaNFKUYQr55kVF6bwcIZgeaLyLclXM_T5w,3891
333
- omlish/http/clients/syncasync.py,sha256=2VfApHxoz3dBilOJhxyaP9LnWWNdqn5G4Tss6CRruXM,1401
334
+ omlish/http/clients/syncasync.py,sha256=nhQlcds4BWib_gphhGElUdA_0uJ0tD1lV1At-fr13Q0,1401
334
335
  omlish/http/clients/urllib.py,sha256=c8TQITZYRo5RrdxaNhsE2QoeDw4mZW0wLzoIsWYTYwU,2852
335
336
  omlish/http/clients/coro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
336
- omlish/http/clients/coro/sync.py,sha256=x5VoA31lPyxGMkuYAXgEziDfL_mhV_VP6iQ4HmpLjjE,5762
337
+ omlish/http/clients/coro/sync.py,sha256=Fk1KdJf9q6ba2-7HWzj7Tb-ibBuj5VIV5f4SCziP-C8,5762
337
338
  omlish/http/coro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
338
339
  omlish/http/coro/io.py,sha256=d97Oa7KlgMP7BQWdhUbOv3sfA96uWyMtjJF7lJFwZC0,1090
339
340
  omlish/http/coro/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -390,10 +391,10 @@ omlish/inject/impl/scopes.py,sha256=sqtjHe6g0pAEOHqIARXGiIE9mKmGmlqh1FDpskV4TIw,
390
391
  omlish/inject/impl/sync.py,sha256=_aLaC-uVNHOePcfE1as61Ni7fuyHZpjEafIWBc7FvC0,1049
391
392
  omlish/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
392
393
  omlish/io/abc.py,sha256=M40QB2udYpCEqmlxCcHv6FlJYJY6ymmJQBlaklYv0U8,1256
393
- omlish/io/buffers.py,sha256=GueMHwOLGd6Q-UxLrGobYYNTm0fc5GBtltwzYgLxGIQ,10935
394
+ omlish/io/buffers.py,sha256=eeDSG0mhwx1IAYPu3pSe-ce3LG5WpQz924OsRyLu0mI,10935
394
395
  omlish/io/fileno.py,sha256=_W3qxkIKpnabn1_7kgmKdx0IsPF3R334xWnF_TtkEj4,185
395
396
  omlish/io/pyio.py,sha256=xmHTV-sn7QZThWCVBo6lTM7dhgsQn7m2L0DqRwdF2-8,94509
396
- omlish/io/readers.py,sha256=O_FcMW6wXSoKhyhM9dKFg9lVJ8mS-Q5I9xnFLfgJMKQ,583
397
+ omlish/io/readers.py,sha256=w67uZQbWzed4Ton2kyA_WRrRz-IRXmFip8EJtpAxLYU,583
397
398
  omlish/io/compress/__init__.py,sha256=fJFPT4ONfqxmsA4jR6qbMt2woIyyEgnc_qOWK9o1kII,247
398
399
  omlish/io/compress/abc.py,sha256=P9YoQX8XYoq2UfBsinKLUuwwqV1ODUIJzjTraRWGF1M,3090
399
400
  omlish/io/compress/adapters.py,sha256=LJHhjwMHXstoLyX_q0QhGoBAcqyYGWfzhzQbGBXHzHY,6148
@@ -635,7 +636,7 @@ omlish/os/pidfiles/__main__.py,sha256=nfjVxFY9I2I7hfb81rCxX6zm2Rc3P87ySpUouy33vG
635
636
  omlish/os/pidfiles/cli.py,sha256=dAKukx4mrarH8t7KZM4n_XSfTk3ycShGAFqRrirK5dw,2079
636
637
  omlish/os/pidfiles/manager.py,sha256=oOnL90ttOG5ba_k9XPJ18reP7M5S-_30ln4OAfYV5W8,2357
637
638
  omlish/os/pidfiles/pidfile.py,sha256=KlqrW7I-lYVDNJspFHE89i_A0HTTteT5B99b7vpTfNs,4400
638
- omlish/os/pidfiles/pinning.py,sha256=WGK0kmMBhH1isGSdpm9fKJQqjaL7kxCuT-aAaaYCyn4,6666
639
+ omlish/os/pidfiles/pinning.py,sha256=JJBpW-9cM9Jv_2l0LP4vXQhpx8EnjFaLUnt1FnxrKhw,6676
639
640
  omlish/reflect/__init__.py,sha256=omD3VLFQtYTwPxrTH6gLATQIax9sTGGKc-7ps96-q0g,1202
640
641
  omlish/reflect/inspect.py,sha256=dUrVz8VfAdLVFtFpW416DxzVC17D80cvFb_g2Odzgq8,1823
641
642
  omlish/reflect/ops.py,sha256=4mGvFMw5D6XGbhDhdCqZ1dZsiqezUTenDv63FIDohpY,3029
@@ -831,6 +832,16 @@ omlish/text/minja.py,sha256=7UKNalkWpTG_364OIo7p5ym--uiNPR2RFBW_W8rrO4I,9194
831
832
  omlish/text/parts.py,sha256=MFi-ANxXlBiOMqW2y2S0BRg6xDwPYxSXSfrYPM3rtcE,6669
832
833
  omlish/text/random.py,sha256=8feS5JE_tSjYlMl-lp0j93kCfzBae9AM2cXlRLebXMA,199
833
834
  omlish/text/templating.py,sha256=i-HU7W-GtKdnBhEG3mnSey5ig7vV0q02D3pVzMoOzJY,3318
835
+ omlish/text/textwrap.py,sha256=fTe04E4T5MpMGLz5QOTfT8T93FdiIT1tohEpzL28_zI,1874
836
+ omlish/text/docwrap/__init__.py,sha256=NQh0gVyJSxUOAX7nnwsAOwHVPeuZ8abVaN5topQI6gE,42
837
+ omlish/text/docwrap/api.py,sha256=cePtl2fLb1FwWN0z6dL5CquAt1AiH0aEEiGp7ijF-Wo,1602
838
+ omlish/text/docwrap/groups.py,sha256=8__fk1q3KX7tRBcWQ2RprpFS2p4QYnRtCNOxbUBaOVI,1887
839
+ omlish/text/docwrap/lists.py,sha256=8ZTfXV-hJaTZTf0jAkODke0Z87kZDS5w-xGqNlc0U_c,4693
840
+ omlish/text/docwrap/parts.py,sha256=IaW_qRvIiP3e12OhFgTIa_qKamQX4MAps-BG5CT_dDw,3338
841
+ omlish/text/docwrap/reflowing.py,sha256=F1mrGpNvTPfII8LpnZGCU4aoUxEvh91iL1mfswHB98U,2584
842
+ omlish/text/docwrap/rendering.py,sha256=SmnkLdePgK4qH0f3lGRY94NfRKJNdTlUaTgzo6RjEnM,2848
843
+ omlish/text/docwrap/utils.py,sha256=n4oFWgA9PBC_77vYrH8H7uFR2s8bcFqDtrCWTcyhGFw,188
844
+ omlish/text/docwrap/wrapping.py,sha256=VyECcYTpjPz7SidT8PJi7mLTg4bGLzaME1H3G5Ub0Yg,1314
834
845
  omlish/text/go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
835
846
  omlish/text/go/quoting.py,sha256=zbcPEDWsdj7GAemtu0x8nwMqpIu2C_5iInSwn6mMWlE,9142
836
847
  omlish/typedvalues/__init__.py,sha256=dQpM8VaON8S7dUv1LBwhyBDjUVo7EW475a9DpnDQz1E,936
@@ -843,9 +854,9 @@ omlish/typedvalues/marshal.py,sha256=2xqX6JllhtGpmeYkU7C-qzgU__0x-vd6CzYbAsocQlc
843
854
  omlish/typedvalues/of_.py,sha256=UXkxSj504WI2UrFlqdZJbu2hyDwBhL7XVrc2qdR02GQ,1309
844
855
  omlish/typedvalues/reflect.py,sha256=PAvKW6T4cW7u--iX80w3HWwZUS3SmIZ2_lQjT65uAyk,1026
845
856
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
846
- omlish-0.0.0.dev473.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
847
- omlish-0.0.0.dev473.dist-info/METADATA,sha256=-EYEBlROnYaFvqD5fxDP0hmJ1GTBnmWbV0EzbBxPACc,18999
848
- omlish-0.0.0.dev473.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
849
- omlish-0.0.0.dev473.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
850
- omlish-0.0.0.dev473.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
851
- omlish-0.0.0.dev473.dist-info/RECORD,,
857
+ omlish-0.0.0.dev474.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
858
+ omlish-0.0.0.dev474.dist-info/METADATA,sha256=sTrBg5y3IV8XQuez3bmFN_mIxOqGGArfERXAKffXCFw,19032
859
+ omlish-0.0.0.dev474.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
860
+ omlish-0.0.0.dev474.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
861
+ omlish-0.0.0.dev474.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
862
+ omlish-0.0.0.dev474.dist-info/RECORD,,