pqx 0.1.0__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.
- pqx/__init__.py +6 -0
- pqx/__main__.py +5 -0
- pqx/_terminal.py +122 -0
- pqx/_version.py +24 -0
- pqx/app.py +1634 -0
- pqx/app.tcss +393 -0
- pqx/cli.py +54 -0
- pqx/data.py +592 -0
- pqx/demo.py +114 -0
- pqx/fmt.py +295 -0
- pqx/plots.py +446 -0
- pqx/screens.py +329 -0
- pqx/widgets.py +43 -0
- pqx-0.1.0.dist-info/METADATA +163 -0
- pqx-0.1.0.dist-info/RECORD +18 -0
- pqx-0.1.0.dist-info/WHEEL +5 -0
- pqx-0.1.0.dist-info/entry_points.txt +2 -0
- pqx-0.1.0.dist-info/top_level.txt +1 -0
pqx/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""pqx — a fast, friendly terminal explorer for Parquet files."""
|
|
2
|
+
|
|
3
|
+
try: # written by setuptools-scm from the git tags at build/install time
|
|
4
|
+
from pqx._version import version as __version__
|
|
5
|
+
except ImportError: # a source checkout that was never built or installed
|
|
6
|
+
__version__ = "0.0.0.dev0"
|
pqx/__main__.py
ADDED
pqx/_terminal.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Work-arounds for terminals and multiplexers that speak older mouse protocols.
|
|
2
|
+
|
|
3
|
+
pqx asks for SGR mouse reporting (mode 1006), like every Textual app. Some
|
|
4
|
+
multiplexers (GNU screen 4.x in particular) don't support SGR and hand the app
|
|
5
|
+
X10-encoded events instead -- ``ESC [ M`` followed by three raw bytes -- or
|
|
6
|
+
urxvt-encoded ones (``ESC [ b;x;y M``). Textual (as of 8.x) has two problems
|
|
7
|
+
with that:
|
|
8
|
+
|
|
9
|
+
* it only parses SGR mouse events, so X10/urxvt clicks are silently dropped;
|
|
10
|
+
* it decodes terminal input as strict UTF-8, and an X10 coordinate above 95
|
|
11
|
+
is a byte > 127 that is not valid UTF-8: the input thread dies with
|
|
12
|
+
``UnicodeDecodeError`` while the app keeps drawing -- it looks frozen.
|
|
13
|
+
|
|
14
|
+
:func:`install` makes input decoding lenient (an invalid byte becomes the
|
|
15
|
+
character with that code, which is exactly what an X10 coordinate means) and
|
|
16
|
+
translates X10/urxvt mouse events into SGR before Textual parses them.
|
|
17
|
+
|
|
18
|
+
It also keeps mouse coordinates in character cells. When a terminal says it
|
|
19
|
+
supports in-band resize reports (mode 2048), Textual switches the mouse to
|
|
20
|
+
pixel coordinates (mode 1016) and relies on those reports to convert back to
|
|
21
|
+
cells. iTerm2 accepts 1016 but doesn't send the reports, so every click lands
|
|
22
|
+
at a pixel position far off-screen and does nothing. Textual guards against
|
|
23
|
+
this only when ``TERM_PROGRAM``/``LC_TERMINAL`` name iTerm, which they don't
|
|
24
|
+
over ssh. pqx never needs sub-cell mouse precision, so pixel mode stays off.
|
|
25
|
+
|
|
26
|
+
Finally, quitting always leaves a clean terminal. Textual draws on the
|
|
27
|
+
alternate screen (mode 1049) and switches back on exit, which restores the
|
|
28
|
+
shell's previous contents. Where the alternate screen is unavailable (some
|
|
29
|
+
ssh/container setups, or a terminal or tmux with it turned off), pqx would
|
|
30
|
+
have drawn over the normal screen and left its last frame behind, with the
|
|
31
|
+
prompt in the middle of it. pqx clears the screen just before switching
|
|
32
|
+
back: invisible when the alternate screen works, a clean screen when not.
|
|
33
|
+
"""
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import codecs
|
|
37
|
+
import functools
|
|
38
|
+
import re
|
|
39
|
+
|
|
40
|
+
_ERRORS = "pqx-bytes"
|
|
41
|
+
_X10 = re.compile(r"\x1b\[M(.)(.)(.)\Z", re.S)
|
|
42
|
+
_URXVT = re.compile(r"\x1b\[(\d+);(\d+);(\d+)M\Z")
|
|
43
|
+
_installed = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _bytes_as_chars(err: UnicodeDecodeError):
|
|
47
|
+
return "".join(chr(b) for b in err.object[err.start:err.end]), err.end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def x10_to_sgr(code: str, last_button: int = 0) -> tuple[str, int] | None:
|
|
51
|
+
"""Translate an X10 or urxvt mouse sequence to SGR. Returns (sgr, last_button)."""
|
|
52
|
+
m = _X10.match(code)
|
|
53
|
+
if m:
|
|
54
|
+
b, x, y = (ord(c) - 32 for c in m.groups())
|
|
55
|
+
else:
|
|
56
|
+
m = _URXVT.match(code)
|
|
57
|
+
if not m:
|
|
58
|
+
return None
|
|
59
|
+
b, x, y = int(m.group(1)) - 32, int(m.group(2)), int(m.group(3))
|
|
60
|
+
if x < 1 or y < 1:
|
|
61
|
+
return None
|
|
62
|
+
if b & 64 or b & 32: # wheel, or motion (with or without a button held)
|
|
63
|
+
return f"\x1b[<{b};{x};{y}M", last_button
|
|
64
|
+
if b & 3 == 3: # X10 release doesn't say which button: use the one pressed last
|
|
65
|
+
return f"\x1b[<{last_button | (b & ~3)};{x};{y}m", last_button
|
|
66
|
+
return f"\x1b[<{b};{x};{y}M", b & 3
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def install() -> None:
|
|
70
|
+
"""Patch Textual's input path (idempotent)."""
|
|
71
|
+
global _installed
|
|
72
|
+
if _installed:
|
|
73
|
+
return
|
|
74
|
+
_installed = True
|
|
75
|
+
try:
|
|
76
|
+
codecs.lookup_error(_ERRORS)
|
|
77
|
+
except LookupError:
|
|
78
|
+
codecs.register_error(_ERRORS, _bytes_as_chars)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
from textual.drivers import linux_driver
|
|
82
|
+
|
|
83
|
+
real = codecs.getincrementaldecoder
|
|
84
|
+
|
|
85
|
+
def lenient(encoding: str):
|
|
86
|
+
return functools.partial(real(encoding), errors=_ERRORS)
|
|
87
|
+
|
|
88
|
+
linux_driver.getincrementaldecoder = lenient
|
|
89
|
+
except Exception: # pragma: no cover - other platforms / Textual versions
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
from textual.drivers.linux_driver import LinuxDriver
|
|
94
|
+
|
|
95
|
+
LinuxDriver._enable_mouse_pixels = lambda self: None # cells, always (see above)
|
|
96
|
+
|
|
97
|
+
stop = LinuxDriver.stop_application_mode
|
|
98
|
+
|
|
99
|
+
@functools.wraps(stop)
|
|
100
|
+
def stop_application_mode(self):
|
|
101
|
+
self.write("\x1b[0m\x1b[H\x1b[2J") # reset attributes, home, clear: before leaving 1049
|
|
102
|
+
stop(self)
|
|
103
|
+
|
|
104
|
+
LinuxDriver.stop_application_mode = stop_application_mode
|
|
105
|
+
except Exception: # pragma: no cover
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
from textual._xterm_parser import XTermParser
|
|
110
|
+
|
|
111
|
+
original = XTermParser.parse_mouse_code
|
|
112
|
+
|
|
113
|
+
@functools.wraps(original)
|
|
114
|
+
def parse_mouse_code(self, code: str):
|
|
115
|
+
translated = x10_to_sgr(code, getattr(self, "_pqx_last_button", 0))
|
|
116
|
+
if translated is not None:
|
|
117
|
+
code, self._pqx_last_button = translated
|
|
118
|
+
return original(self, code)
|
|
119
|
+
|
|
120
|
+
XTermParser.parse_mouse_code = parse_mouse_code
|
|
121
|
+
except Exception: # pragma: no cover
|
|
122
|
+
pass
|
pqx/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g006519c4f'
|