omlish 0.0.0.dev470__py3-none-any.whl → 0.0.0.dev471__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/__about__.py +2 -2
- omlish/inject/__init__.py +4 -0
- omlish/inject/elements.py +17 -0
- omlish/inject/impl/elements.py +17 -2
- omlish/inject/impl/injector.py +17 -19
- omlish/inject/impl/maysync.py +3 -4
- omlish/inject/impl/sync.py +3 -4
- omlish/inject/injector.py +31 -2
- omlish/inject/maysync.py +2 -4
- omlish/inject/sync.py +5 -4
- omlish/lang/imports/proxy.py +10 -1
- omlish/term/pager.py +235 -0
- omlish/term/terminfo.py +935 -0
- omlish/term/termstate.py +67 -0
- {omlish-0.0.0.dev470.dist-info → omlish-0.0.0.dev471.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev470.dist-info → omlish-0.0.0.dev471.dist-info}/RECORD +21 -18
- /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
- {omlish-0.0.0.dev470.dist-info → omlish-0.0.0.dev471.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev470.dist-info → omlish-0.0.0.dev471.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev470.dist-info → omlish-0.0.0.dev471.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev470.dist-info → omlish-0.0.0.dev471.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/inject/__init__.py
CHANGED
omlish/inject/elements.py
CHANGED
|
@@ -6,6 +6,12 @@ from .. import lang
|
|
|
6
6
|
from .impl.origins import HasOriginsImpl
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
if ta.TYPE_CHECKING:
|
|
10
|
+
from .impl import elements as _elements
|
|
11
|
+
else:
|
|
12
|
+
_elements = lang.proxy_import('.impl.elements', __package__)
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
##
|
|
10
16
|
|
|
11
17
|
|
|
@@ -74,3 +80,14 @@ def iter_elements(*args: Elemental) -> ta.Iterator[Element]:
|
|
|
74
80
|
yield from a
|
|
75
81
|
else:
|
|
76
82
|
raise TypeError(a)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CollectedElements(lang.PackageSealed, lang.Abstract):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def collect_elements(es: Elements | CollectedElements) -> CollectedElements:
|
|
93
|
+
return _elements.collect_elements(es)
|
omlish/inject/impl/elements.py
CHANGED
|
@@ -27,6 +27,7 @@ from ... import collections as col
|
|
|
27
27
|
from ... import lang
|
|
28
28
|
from ..bindings import Binding
|
|
29
29
|
from ..eagers import Eager
|
|
30
|
+
from ..elements import CollectedElements
|
|
30
31
|
from ..elements import Element
|
|
31
32
|
from ..elements import Elements
|
|
32
33
|
from ..errors import ConflictingKeyError
|
|
@@ -47,7 +48,7 @@ from .multis import make_multi_provider_impl
|
|
|
47
48
|
from .origins import Origins
|
|
48
49
|
from .origins import set_origins
|
|
49
50
|
from .providers import ProviderImpl
|
|
50
|
-
from .
|
|
51
|
+
from .providersmap import make_provider_impl
|
|
51
52
|
from .scopes import make_scope_impl
|
|
52
53
|
|
|
53
54
|
|
|
@@ -80,7 +81,7 @@ _NON_BINDING_ELEMENT_TYPES: tuple[type[Element], ...] = (
|
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
class ElementCollection(lang.Final):
|
|
84
|
+
class ElementCollection(CollectedElements, lang.Final):
|
|
84
85
|
def __init__(self, es: Elements) -> None:
|
|
85
86
|
super().__init__()
|
|
86
87
|
|
|
@@ -208,6 +209,10 @@ class ElementCollection(lang.Final):
|
|
|
208
209
|
|
|
209
210
|
##
|
|
210
211
|
|
|
212
|
+
@lang.cached_function
|
|
213
|
+
def scope_binding_scopes(self) -> ta.Sequence[Scope]:
|
|
214
|
+
return [sb.scope for sb in self.elements_of_type(ScopeBinding)]
|
|
215
|
+
|
|
211
216
|
@lang.cached_function
|
|
212
217
|
def eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
|
|
213
218
|
bim = self.binding_impl_map()
|
|
@@ -216,3 +221,13 @@ class ElementCollection(lang.Final):
|
|
|
216
221
|
bi = bim[e.key]
|
|
217
222
|
ret.setdefault(bi.scope, []).append(e.key)
|
|
218
223
|
return ret
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
##
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def collect_elements(es: Elements | CollectedElements) -> ElementCollection:
|
|
230
|
+
if isinstance(es, CollectedElements):
|
|
231
|
+
return check.isinstance(es, ElementCollection)
|
|
232
|
+
else:
|
|
233
|
+
return ElementCollection(es)
|
omlish/inject/impl/injector.py
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
TODO:
|
|
3
3
|
- ** can currently bind in a child/private scope shadowing an external parent binding **
|
|
4
4
|
- better source tracking
|
|
5
|
-
- cache/export ElementCollections lol
|
|
6
5
|
- scope bindings, auto in root
|
|
7
6
|
- injector-internal / blacklisted bindings (Injector itself, default scopes) without rebuilding ElementCollection
|
|
8
7
|
- config - proxies, impl select, etc
|
|
9
8
|
- config is probably shared with ElementCollection... but not 'bound', must be shared everywhere
|
|
10
9
|
- InjectorRoot object?
|
|
11
10
|
- ** eagers in any scope, on scope init/open
|
|
12
|
-
- injection listeners
|
|
13
11
|
- unions - raise on ambiguous - usecase: sql.AsyncEngineLike
|
|
14
12
|
- multiple live request scopes on single injector - use private injectors?
|
|
15
13
|
- more listeners - UnboundKeyListener
|
|
@@ -24,7 +22,7 @@ import weakref
|
|
|
24
22
|
from ... import check
|
|
25
23
|
from ... import lang
|
|
26
24
|
from ...logs import all as logs
|
|
27
|
-
from ..elements import
|
|
25
|
+
from ..elements import CollectedElements
|
|
28
26
|
from ..errors import CyclicDependencyError
|
|
29
27
|
from ..errors import UnboundKeyError
|
|
30
28
|
from ..injector import AsyncInjector
|
|
@@ -33,7 +31,6 @@ from ..keys import Key
|
|
|
33
31
|
from ..keys import as_key
|
|
34
32
|
from ..listeners import ProvisionListener
|
|
35
33
|
from ..listeners import ProvisionListenerBinding
|
|
36
|
-
from ..scopes import ScopeBinding
|
|
37
34
|
from ..scopes import Singleton
|
|
38
35
|
from ..scopes import ThreadScope
|
|
39
36
|
from ..types import Scope
|
|
@@ -61,14 +58,12 @@ DEFAULT_SCOPES: list[Scope] = [
|
|
|
61
58
|
class AsyncInjectorImpl(AsyncInjector, lang.Final):
|
|
62
59
|
def __init__(
|
|
63
60
|
self,
|
|
64
|
-
ec:
|
|
61
|
+
ec: CollectedElements,
|
|
65
62
|
p: ta.Optional['AsyncInjectorImpl'] = None,
|
|
66
63
|
*,
|
|
67
64
|
internal_consts: dict[Key, ta.Any] | None = None,
|
|
68
65
|
) -> None:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
self._ec = check.isinstance(ec, ElementCollection)
|
|
66
|
+
self._ec = (ec := check.isinstance(ec, ElementCollection))
|
|
72
67
|
self._p: AsyncInjectorImpl | None = check.isinstance(p, (AsyncInjectorImpl, None))
|
|
73
68
|
|
|
74
69
|
self._internal_consts: dict[Key, ta.Any] = {
|
|
@@ -77,28 +72,31 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
|
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
self._bim = ec.binding_impl_map()
|
|
75
|
+
|
|
80
76
|
self._ekbs = ec.eager_keys_by_scope()
|
|
77
|
+
|
|
81
78
|
self._pls: tuple[ProvisionListener, ...] = tuple(
|
|
82
79
|
b.listener # type: ignore[attr-defined]
|
|
83
80
|
for b in itertools.chain(
|
|
84
81
|
ec.elements_of_type(ProvisionListenerBinding),
|
|
85
|
-
|
|
82
|
+
p._pls if p is not None else (), # noqa
|
|
86
83
|
)
|
|
87
84
|
)
|
|
88
85
|
|
|
89
|
-
self._cs: weakref.WeakSet[AsyncInjectorImpl] | None = None # noqa
|
|
90
86
|
self._root: AsyncInjectorImpl = p._root if p is not None else self # noqa
|
|
91
87
|
|
|
92
|
-
self.__cur_req: AsyncInjectorImpl._Request | None = None
|
|
93
|
-
|
|
94
|
-
ss = [
|
|
95
|
-
*DEFAULT_SCOPES,
|
|
96
|
-
*[sb.scope for sb in ec.elements_of_type(ScopeBinding)],
|
|
97
|
-
]
|
|
98
88
|
self._scopes: dict[Scope, ScopeImpl] = {
|
|
99
|
-
s: make_scope_impl(s)
|
|
89
|
+
s: make_scope_impl(s)
|
|
90
|
+
for s in itertools.chain(
|
|
91
|
+
DEFAULT_SCOPES,
|
|
92
|
+
ec.scope_binding_scopes(),
|
|
93
|
+
)
|
|
100
94
|
}
|
|
101
95
|
|
|
96
|
+
_cs: weakref.WeakSet['AsyncInjectorImpl'] | None = None # noqa
|
|
97
|
+
|
|
98
|
+
__cur_req: ta.Optional['AsyncInjectorImpl._Request'] = None
|
|
99
|
+
|
|
102
100
|
#
|
|
103
101
|
|
|
104
102
|
_has_run_init: bool = False
|
|
@@ -259,7 +257,7 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
|
|
|
259
257
|
return obj(**kws)
|
|
260
258
|
|
|
261
259
|
|
|
262
|
-
async def create_async_injector(
|
|
263
|
-
i = AsyncInjectorImpl(
|
|
260
|
+
async def create_async_injector(ce: CollectedElements) -> AsyncInjector:
|
|
261
|
+
i = AsyncInjectorImpl(ce)
|
|
264
262
|
await i._init() # noqa
|
|
265
263
|
return i
|
omlish/inject/impl/maysync.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import typing as ta
|
|
2
2
|
|
|
3
3
|
from ... import lang
|
|
4
|
-
from ..elements import
|
|
4
|
+
from ..elements import CollectedElements
|
|
5
5
|
from ..injector import AsyncInjector
|
|
6
6
|
from ..inspect import KwargsTarget
|
|
7
7
|
from ..keys import Key
|
|
8
8
|
from ..maysync import MaysyncInjector
|
|
9
9
|
from ..sync import Injector
|
|
10
|
-
from .elements import ElementCollection
|
|
11
10
|
from .injector import AsyncInjectorImpl
|
|
12
11
|
|
|
13
12
|
|
|
@@ -30,10 +29,10 @@ class MaysyncInjectorImpl(MaysyncInjector, lang.Final):
|
|
|
30
29
|
return lang.run_maysync(self._ai.inject(obj))
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
def create_maysync_injector(
|
|
32
|
+
def create_maysync_injector(ce: CollectedElements) -> MaysyncInjector:
|
|
34
33
|
si = MaysyncInjectorImpl()
|
|
35
34
|
ai = AsyncInjectorImpl(
|
|
36
|
-
|
|
35
|
+
ce,
|
|
37
36
|
internal_consts={
|
|
38
37
|
Key(MaysyncInjector): si,
|
|
39
38
|
Key(Injector): si,
|
omlish/inject/impl/sync.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import typing as ta
|
|
2
2
|
|
|
3
3
|
from ... import lang
|
|
4
|
-
from ..elements import
|
|
4
|
+
from ..elements import CollectedElements
|
|
5
5
|
from ..injector import AsyncInjector
|
|
6
6
|
from ..inspect import KwargsTarget
|
|
7
7
|
from ..keys import Key
|
|
8
8
|
from ..sync import Injector
|
|
9
|
-
from .elements import ElementCollection
|
|
10
9
|
from .injector import AsyncInjectorImpl
|
|
11
10
|
|
|
12
11
|
|
|
@@ -29,10 +28,10 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
29
28
|
return lang.sync_await(self._ai.inject(obj))
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
def create_injector(
|
|
31
|
+
def create_injector(ce: CollectedElements) -> Injector:
|
|
33
32
|
si = InjectorImpl()
|
|
34
33
|
ai = AsyncInjectorImpl(
|
|
35
|
-
|
|
34
|
+
ce,
|
|
36
35
|
internal_consts={
|
|
37
36
|
Key(Injector): si,
|
|
38
37
|
},
|
omlish/inject/injector.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import typing as ta
|
|
3
3
|
|
|
4
|
+
from .. import check
|
|
4
5
|
from .. import lang
|
|
6
|
+
from .elements import CollectedElements
|
|
5
7
|
from .elements import Elemental
|
|
6
8
|
from .elements import as_elements
|
|
9
|
+
from .elements import collect_elements
|
|
7
10
|
from .inspect import KwargsTarget
|
|
8
11
|
from .keys import Key
|
|
9
12
|
|
|
@@ -44,5 +47,31 @@ class AsyncInjector(lang.Abstract):
|
|
|
44
47
|
return self.provide(target)
|
|
45
48
|
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
##
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@ta.final
|
|
54
|
+
class _InjectorCreator(ta.Generic[T]):
|
|
55
|
+
def __init__(self, fac: ta.Callable[[CollectedElements], T]) -> None:
|
|
56
|
+
self._fac = fac
|
|
57
|
+
|
|
58
|
+
@ta.overload
|
|
59
|
+
def __call__(self, es: CollectedElements, /) -> T: ...
|
|
60
|
+
|
|
61
|
+
@ta.overload
|
|
62
|
+
def __call__(self, *es: Elemental) -> T: ...
|
|
63
|
+
|
|
64
|
+
def __call__(self, arg0, *argv):
|
|
65
|
+
ce: CollectedElements
|
|
66
|
+
if isinstance(arg0, CollectedElements):
|
|
67
|
+
check.arg(not argv)
|
|
68
|
+
ce = arg0
|
|
69
|
+
else:
|
|
70
|
+
ce = collect_elements(as_elements(arg0, *argv))
|
|
71
|
+
return self._fac(ce)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
create_async_injector = _InjectorCreator[ta.Awaitable[AsyncInjector]](lambda ce: _injector.create_async_injector(ce))
|
omlish/inject/maysync.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import typing as ta
|
|
2
2
|
|
|
3
3
|
from .. import lang
|
|
4
|
-
from .
|
|
5
|
-
from .elements import as_elements
|
|
4
|
+
from .injector import _InjectorCreator
|
|
6
5
|
from .sync import Injector
|
|
7
6
|
|
|
8
7
|
|
|
@@ -25,5 +24,4 @@ class MaysyncInjector(Injector, lang.Abstract):
|
|
|
25
24
|
##
|
|
26
25
|
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
return _maysync.create_maysync_injector(as_elements(*args))
|
|
27
|
+
create_maysync_injector = _InjectorCreator[MaysyncInjector](lambda ce: _maysync.create_maysync_injector(ce))
|
omlish/inject/sync.py
CHANGED
|
@@ -2,8 +2,7 @@ import abc
|
|
|
2
2
|
import typing as ta
|
|
3
3
|
|
|
4
4
|
from .. import lang
|
|
5
|
-
from .
|
|
6
|
-
from .elements import as_elements
|
|
5
|
+
from .injector import _InjectorCreator
|
|
7
6
|
from .inspect import KwargsTarget
|
|
8
7
|
from .keys import Key
|
|
9
8
|
|
|
@@ -44,5 +43,7 @@ class Injector(lang.Abstract):
|
|
|
44
43
|
return self.provide(target)
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
##
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
create_injector = _InjectorCreator[Injector](lambda ce: _sync.create_injector(ce))
|
omlish/lang/imports/proxy.py
CHANGED
|
@@ -342,6 +342,8 @@ def proxy_import(
|
|
|
342
342
|
spec: str,
|
|
343
343
|
package: str | None = None,
|
|
344
344
|
extras: ta.Iterable[str] | None = None,
|
|
345
|
+
*,
|
|
346
|
+
no_cache: bool = False,
|
|
345
347
|
) -> types.ModuleType:
|
|
346
348
|
"""'Legacy' proxy import mechanism."""
|
|
347
349
|
|
|
@@ -352,12 +354,19 @@ def proxy_import(
|
|
|
352
354
|
|
|
353
355
|
def __getattr__(att): # noqa
|
|
354
356
|
nonlocal omod
|
|
357
|
+
|
|
355
358
|
if omod is None:
|
|
356
359
|
omod = importlib.import_module(spec, package=package)
|
|
357
360
|
if extras:
|
|
358
361
|
for x in extras:
|
|
359
362
|
importlib.import_module(f'{spec}.{x}', package=package)
|
|
360
|
-
|
|
363
|
+
|
|
364
|
+
v = getattr(omod, att)
|
|
365
|
+
|
|
366
|
+
if not no_cache:
|
|
367
|
+
setattr(lmod, att, v)
|
|
368
|
+
|
|
369
|
+
return v
|
|
361
370
|
|
|
362
371
|
lmod = types.ModuleType(spec)
|
|
363
372
|
lmod.__getattr__ = __getattr__ # type: ignore[method-assign]
|
omlish/term/pager.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# ruff: noqa: S602 S605
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
|
|
4
|
+
# Armin Rigo
|
|
5
|
+
#
|
|
6
|
+
# All Rights Reserved
|
|
7
|
+
#
|
|
8
|
+
#
|
|
9
|
+
# Permission to use, copy, modify, and distribute this software and its documentation for any purpose is hereby granted
|
|
10
|
+
# without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and
|
|
11
|
+
# this permission notice appear in supporting documentation.
|
|
12
|
+
#
|
|
13
|
+
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
|
|
14
|
+
# MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES
|
|
15
|
+
# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
16
|
+
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
17
|
+
#
|
|
18
|
+
# https://github.com/python/cpython/tree/bcced02604f845b2b71d0a1dd95f95366bd7774d/Lib/_pyrepl
|
|
19
|
+
import io
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import tempfile
|
|
25
|
+
import termios
|
|
26
|
+
import tty
|
|
27
|
+
import typing as ta
|
|
28
|
+
|
|
29
|
+
from .. import check
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Pager(ta.Protocol):
|
|
36
|
+
def __call__(self, text: str, title: str = '') -> None:
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_pager() -> Pager:
|
|
41
|
+
"""Decide what method to use for paging through text."""
|
|
42
|
+
|
|
43
|
+
if not hasattr(sys.stdin, 'isatty'):
|
|
44
|
+
return plain_pager
|
|
45
|
+
|
|
46
|
+
if not hasattr(sys.stdout, 'isatty'):
|
|
47
|
+
return plain_pager
|
|
48
|
+
|
|
49
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
50
|
+
return plain_pager
|
|
51
|
+
|
|
52
|
+
if sys.platform == 'emscripten':
|
|
53
|
+
return plain_pager
|
|
54
|
+
|
|
55
|
+
use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER')
|
|
56
|
+
if use_pager:
|
|
57
|
+
if sys.platform == 'win32': # pipes completely broken in Windows
|
|
58
|
+
raise OSError
|
|
59
|
+
|
|
60
|
+
elif os.environ.get('TERM') in ('dumb', 'emacs'):
|
|
61
|
+
return lambda text, title='': pipe_pager(plain(text), use_pager, title)
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
return lambda text, title='': pipe_pager(text, use_pager, title)
|
|
65
|
+
|
|
66
|
+
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
|
67
|
+
return plain_pager
|
|
68
|
+
|
|
69
|
+
if sys.platform == 'win32':
|
|
70
|
+
return lambda text, title='': tempfile_pager(plain(text), 'more <')
|
|
71
|
+
|
|
72
|
+
if hasattr(os, 'system') and os.system('(pager) 2>/dev/null') == 0:
|
|
73
|
+
return lambda text, title='': pipe_pager(text, 'pager', title)
|
|
74
|
+
|
|
75
|
+
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
|
76
|
+
return lambda text, title='': pipe_pager(text, 'less', title)
|
|
77
|
+
|
|
78
|
+
import tempfile
|
|
79
|
+
(fd, filename) = tempfile.mkstemp()
|
|
80
|
+
os.close(fd)
|
|
81
|
+
try:
|
|
82
|
+
if hasattr(os, 'system') and os.system(f'more "{filename}"') == 0:
|
|
83
|
+
return lambda text, title='': pipe_pager(text, 'more', title)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
return tty_pager
|
|
87
|
+
|
|
88
|
+
finally:
|
|
89
|
+
os.unlink(filename)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def escape_stdout(text: str) -> str:
|
|
93
|
+
# Escape non-encodable characters to avoid encoding errors later
|
|
94
|
+
encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
|
|
95
|
+
return text.encode(encoding, 'backslashreplace').decode(encoding)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def escape_less(s: str) -> str:
|
|
99
|
+
return re.sub(r'([?:.%\\])', r'\\\1', s)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def plain(text: str) -> str:
|
|
103
|
+
"""Remove boldface formatting from text."""
|
|
104
|
+
|
|
105
|
+
return re.sub('.\b', '', text)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def tty_pager(text: str, title: str = '') -> None:
|
|
109
|
+
"""Page through text on a text terminal."""
|
|
110
|
+
|
|
111
|
+
lines = plain(escape_stdout(text)).split('\n')
|
|
112
|
+
has_tty = False
|
|
113
|
+
try:
|
|
114
|
+
fd = sys.stdin.fileno()
|
|
115
|
+
old = termios.tcgetattr(fd)
|
|
116
|
+
tty.setcbreak(fd)
|
|
117
|
+
has_tty = True
|
|
118
|
+
|
|
119
|
+
def getchar() -> str:
|
|
120
|
+
return sys.stdin.read(1)
|
|
121
|
+
|
|
122
|
+
except (ImportError, AttributeError, io.UnsupportedOperation):
|
|
123
|
+
def getchar() -> str:
|
|
124
|
+
return sys.stdin.readline()[:-1][:1]
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
try:
|
|
128
|
+
h = int(os.environ.get('LINES', '0'))
|
|
129
|
+
except ValueError:
|
|
130
|
+
h = 0
|
|
131
|
+
if h <= 1:
|
|
132
|
+
h = 25
|
|
133
|
+
r = inc = h - 1
|
|
134
|
+
|
|
135
|
+
sys.stdout.write('\n'.join(lines[:inc]) + '\n')
|
|
136
|
+
while lines[r:]:
|
|
137
|
+
sys.stdout.write('-- more --')
|
|
138
|
+
sys.stdout.flush()
|
|
139
|
+
c = getchar()
|
|
140
|
+
|
|
141
|
+
if c in ('q', 'Q'):
|
|
142
|
+
sys.stdout.write('\r \r')
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
elif c in ('\r', '\n'):
|
|
146
|
+
sys.stdout.write('\r \r' + lines[r] + '\n')
|
|
147
|
+
r = r + 1
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if c in ('b', 'B', '\x1b'):
|
|
151
|
+
r = r - inc - inc
|
|
152
|
+
if r < 0:
|
|
153
|
+
r = 0
|
|
154
|
+
|
|
155
|
+
sys.stdout.write('\n' + '\n'.join(lines[r:r + inc]) + '\n')
|
|
156
|
+
r = r + inc
|
|
157
|
+
|
|
158
|
+
finally:
|
|
159
|
+
if has_tty:
|
|
160
|
+
termios.tcsetattr(fd, termios.TCSAFLUSH, old) # noqa
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def plain_pager(text: str, title: str = '') -> None:
|
|
164
|
+
"""Simply print unformatted text. This is the ultimate fallback."""
|
|
165
|
+
|
|
166
|
+
sys.stdout.write(plain(escape_stdout(text)))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def pipe_pager(text: str, cmd: str, title: str = '') -> None:
|
|
170
|
+
"""Page through text by feeding it to another program."""
|
|
171
|
+
|
|
172
|
+
env = os.environ.copy()
|
|
173
|
+
|
|
174
|
+
if title:
|
|
175
|
+
title += ' '
|
|
176
|
+
esc_title = escape_less(title)
|
|
177
|
+
|
|
178
|
+
prompt_string = (
|
|
179
|
+
f' {esc_title}'
|
|
180
|
+
'?ltline %lt?L/%L.'
|
|
181
|
+
':byte %bB?s/%s.'
|
|
182
|
+
'.'
|
|
183
|
+
'?e (END):?pB %pB\\%..'
|
|
184
|
+
' (press h for help or q to quit)'
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
env['LESS'] = f'-RmPm{prompt_string}$PM{prompt_string}$'
|
|
188
|
+
|
|
189
|
+
proc = subprocess.Popen(
|
|
190
|
+
cmd,
|
|
191
|
+
shell=True,
|
|
192
|
+
stdin=subprocess.PIPE,
|
|
193
|
+
errors='backslashreplace',
|
|
194
|
+
env=env,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
with check.not_none(proc.stdin) as pipe:
|
|
199
|
+
try:
|
|
200
|
+
pipe.write(text)
|
|
201
|
+
|
|
202
|
+
except KeyboardInterrupt:
|
|
203
|
+
# We've hereby abandoned whatever text hasn't been written, but the pager is still in control of the
|
|
204
|
+
# terminal.
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
except OSError:
|
|
208
|
+
pass # Ignore broken pipes caused by quitting the pager program.
|
|
209
|
+
|
|
210
|
+
while True:
|
|
211
|
+
try:
|
|
212
|
+
proc.wait()
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
except KeyboardInterrupt:
|
|
216
|
+
# Ignore ctl-c like the pager itself does. Otherwise the pager is left running and the terminal is in raw
|
|
217
|
+
# mode and unusable.
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def tempfile_pager(text: str, cmd: str, title: str = '') -> None:
|
|
222
|
+
"""Page through text by invoking a program on a temporary file."""
|
|
223
|
+
|
|
224
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
225
|
+
filename = os.path.join(tempdir, 'pydoc.out')
|
|
226
|
+
|
|
227
|
+
with open(
|
|
228
|
+
filename,
|
|
229
|
+
'w',
|
|
230
|
+
errors='backslashreplace',
|
|
231
|
+
encoding=os.device_encoding(0) if sys.platform == 'win32' else None,
|
|
232
|
+
) as file:
|
|
233
|
+
file.write(text)
|
|
234
|
+
|
|
235
|
+
os.system(cmd + ' "' + filename + '"')
|