omlish 0.0.0.dev469__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.
@@ -0,0 +1,935 @@
1
+ # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
2
+ # --------------------------------------------
3
+ #
4
+ # 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
5
+ # ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
6
+ # documentation.
7
+ #
8
+ # 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
9
+ # royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
10
+ # works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
11
+ # Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
12
+ # 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights Reserved" are retained in Python
13
+ # alone or in any derivative version prepared by Licensee.
14
+ #
15
+ # 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
16
+ # wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
17
+ # any such work a brief summary of the changes made to Python.
18
+ #
19
+ # 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
20
+ # EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
21
+ # OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
22
+ # RIGHTS.
23
+ #
24
+ # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
25
+ # DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
26
+ # ADVISED OF THE POSSIBILITY THEREOF.
27
+ #
28
+ # 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
29
+ #
30
+ # 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
31
+ # venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
32
+ # name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
33
+ #
34
+ # 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
35
+ # License Agreement.
36
+ # https://github.com/python/cpython/blob/ce4b0ede16aea62ee7b1e02df7e1538102a356da/Lib/_pyrepl/terminfo.py
37
+ """Pure Python curses-like terminal capability queries."""
38
+ import dataclasses as dc
39
+ import errno
40
+ import os
41
+ import pathlib
42
+ import re
43
+ import struct
44
+ import typing as ta
45
+
46
+
47
+ ##
48
+
49
+
50
+ # Special values for absent/cancelled capabilities
51
+ ABSENT_BOOLEAN = -1
52
+ ABSENT_NUMERIC = -1
53
+ CANCELLED_NUMERIC = -2
54
+ ABSENT_STRING = None
55
+ CANCELLED_STRING = None
56
+
57
+
58
+ ##
59
+
60
+
61
+ # Hard-coded terminal capabilities for common terminals. This is a minimal subset needed by PyREPL.
62
+ _FALLBACK_TERMINAL_CAPABILITIES: ta.Mapping[str, ta.Mapping[str, bytes]] = {
63
+
64
+ # ANSI/xterm-compatible terminals
65
+ 'ansi': {
66
+ # Bell
67
+ 'bel': b'\x07',
68
+
69
+ # Cursor movement
70
+ 'cub': b'\x1b[%p1%dD', # Move cursor left N columns
71
+ 'cud': b'\x1b[%p1%dB', # Move cursor down N rows
72
+ 'cuf': b'\x1b[%p1%dC', # Move cursor right N columns
73
+ 'cuu': b'\x1b[%p1%dA', # Move cursor up N rows
74
+ 'cub1': b'\x08', # Move cursor left 1 column
75
+ 'cud1': b'\n', # Move cursor down 1 row
76
+ 'cuf1': b'\x1b[C', # Move cursor right 1 column
77
+ 'cuu1': b'\x1b[A', # Move cursor up 1 row
78
+ 'cup': b'\x1b[%i%p1%d;%p2%dH', # Move cursor to row, column
79
+ 'hpa': b'\x1b[%i%p1%dG', # Move cursor to column
80
+ # Clear operations
81
+ 'clear': b'\x1b[H\x1b[2J', # Clear screen and home cursor
82
+ 'el': b'\x1b[K', # Clear to end of line
83
+
84
+ # Insert/delete
85
+ 'dch': b'\x1b[%p1%dP', # Delete N characters
86
+ 'dch1': b'\x1b[P', # Delete 1 character
87
+ 'ich': b'\x1b[%p1%d@', # Insert N characters
88
+ 'ich1': b'', # Insert 1 character
89
+
90
+ # Cursor visibility
91
+ 'civis': b'\x1b[?25l', # Make cursor invisible
92
+ 'cnorm': b'\x1b[?12l\x1b[?25h', # Make cursor normal (visible)
93
+
94
+ # Scrolling
95
+ 'ind': b'\n', # Scroll up one line
96
+ 'ri': b'\x1bM', # Scroll down one line
97
+
98
+ # Keypad mode
99
+ 'smkx': b'\x1b[?1h\x1b=', # Enable keypad mode
100
+ 'rmkx': b'\x1b[?1l\x1b>', # Disable keypad mode
101
+
102
+ # Padding (not used in modern terminals)
103
+ 'pad': b'',
104
+
105
+ # Function keys and special keys
106
+ 'kdch1': b'\x1b[3~', # Delete key
107
+ 'kcud1': b'\x1bOB', # Down arrow
108
+ 'kend': b'\x1bOF', # End key
109
+ 'kent': b'\x1bOM', # Enter key
110
+ 'khome': b'\x1bOH', # Home key
111
+ 'kich1': b'\x1b[2~', # Insert key
112
+ 'kcub1': b'\x1bOD', # Left arrow
113
+ 'knp': b'\x1b[6~', # Page down
114
+ 'kpp': b'\x1b[5~', # Page up
115
+ 'kcuf1': b'\x1bOC', # Right arrow
116
+ 'kcuu1': b'\x1bOA', # Up arrow
117
+
118
+ # Function keys F1-F20
119
+ 'kf1': b'\x1bOP',
120
+ 'kf2': b'\x1bOQ',
121
+ 'kf3': b'\x1bOR',
122
+ 'kf4': b'\x1bOS',
123
+ 'kf5': b'\x1b[15~',
124
+ 'kf6': b'\x1b[17~',
125
+ 'kf7': b'\x1b[18~',
126
+ 'kf8': b'\x1b[19~',
127
+ 'kf9': b'\x1b[20~',
128
+ 'kf10': b'\x1b[21~',
129
+ 'kf11': b'\x1b[23~',
130
+ 'kf12': b'\x1b[24~',
131
+ 'kf13': b'\x1b[1;2P',
132
+ 'kf14': b'\x1b[1;2Q',
133
+ 'kf15': b'\x1b[1;2R',
134
+ 'kf16': b'\x1b[1;2S',
135
+ 'kf17': b'\x1b[15;2~',
136
+ 'kf18': b'\x1b[17;2~',
137
+ 'kf19': b'\x1b[18;2~',
138
+ 'kf20': b'\x1b[19;2~',
139
+ },
140
+
141
+ # Dumb terminal - minimal capabilities
142
+ 'dumb': {
143
+ 'bel': b'\x07', # Bell
144
+ 'cud1': b'\n', # Move down 1 row (newline)
145
+ 'ind': b'\n', # Scroll up one line (newline)
146
+ },
147
+
148
+ # Linux console
149
+ 'linux': {
150
+ # Bell
151
+ 'bel': b'\x07',
152
+
153
+ # Cursor movement
154
+ 'cub': b'\x1b[%p1%dD', # Move cursor left N columns
155
+ 'cud': b'\x1b[%p1%dB', # Move cursor down N rows
156
+ 'cuf': b'\x1b[%p1%dC', # Move cursor right N columns
157
+ 'cuu': b'\x1b[%p1%dA', # Move cursor up N rows
158
+ 'cub1': b'\x08', # Move cursor left 1 column (backspace)
159
+ 'cud1': b'\n', # Move cursor down 1 row (newline)
160
+ 'cuf1': b'\x1b[C', # Move cursor right 1 column
161
+ 'cuu1': b'\x1b[A', # Move cursor up 1 row
162
+ 'cup': b'\x1b[%i%p1%d;%p2%dH', # Move cursor to row, column
163
+ 'hpa': b'\x1b[%i%p1%dG', # Move cursor to column
164
+
165
+ # Clear operations
166
+ 'clear': b'\x1b[H\x1b[J', # Clear screen and home cursor (different from ansi!)
167
+ 'el': b'\x1b[K', # Clear to end of line
168
+
169
+ # Insert/delete
170
+ 'dch': b'\x1b[%p1%dP', # Delete N characters
171
+ 'dch1': b'\x1b[P', # Delete 1 character
172
+ 'ich': b'\x1b[%p1%d@', # Insert N characters
173
+ 'ich1': b'\x1b[@', # Insert 1 character
174
+
175
+ # Cursor visibility
176
+ 'civis': b'\x1b[?25l\x1b[?1c', # Make cursor invisible
177
+ 'cnorm': b'\x1b[?25h\x1b[?0c', # Make cursor normal
178
+
179
+ # Scrolling
180
+ 'ind': b'\n', # Scroll up one line
181
+ 'ri': b'\x1bM', # Scroll down one line
182
+
183
+ # Keypad mode
184
+ 'smkx': b'\x1b[?1h\x1b=', # Enable keypad mode
185
+ 'rmkx': b'\x1b[?1l\x1b>', # Disable keypad mode
186
+
187
+ # Function keys and special keys
188
+ 'kdch1': b'\x1b[3~', # Delete key
189
+ 'kcud1': b'\x1b[B', # Down arrow
190
+ 'kend': b'\x1b[4~', # End key (different from ansi!)
191
+ 'khome': b'\x1b[1~', # Home key (different from ansi!)
192
+ 'kich1': b'\x1b[2~', # Insert key
193
+ 'kcub1': b'\x1b[D', # Left arrow
194
+ 'knp': b'\x1b[6~', # Page down
195
+ 'kpp': b'\x1b[5~', # Page up
196
+ 'kcuf1': b'\x1b[C', # Right arrow
197
+ 'kcuu1': b'\x1b[A', # Up arrow
198
+
199
+ # Function keys
200
+ 'kf1': b'\x1b[[A',
201
+ 'kf2': b'\x1b[[B',
202
+ 'kf3': b'\x1b[[C',
203
+ 'kf4': b'\x1b[[D',
204
+ 'kf5': b'\x1b[[E',
205
+ 'kf6': b'\x1b[17~',
206
+ 'kf7': b'\x1b[18~',
207
+ 'kf8': b'\x1b[19~',
208
+ 'kf9': b'\x1b[20~',
209
+ 'kf10': b'\x1b[21~',
210
+ 'kf11': b'\x1b[23~',
211
+ 'kf12': b'\x1b[24~',
212
+ 'kf13': b'\x1b[25~',
213
+ 'kf14': b'\x1b[26~',
214
+ 'kf15': b'\x1b[28~',
215
+ 'kf16': b'\x1b[29~',
216
+ 'kf17': b'\x1b[31~',
217
+ 'kf18': b'\x1b[32~',
218
+ 'kf19': b'\x1b[33~',
219
+ 'kf20': b'\x1b[34~',
220
+ },
221
+
222
+ }
223
+
224
+
225
+ # Map common TERM values to capability sets
226
+ _TERM_ALIASES: ta.Mapping[str, str] = {
227
+ 'xterm': 'ansi',
228
+ 'xterm-color': 'ansi',
229
+ 'xterm-256color': 'ansi',
230
+ 'screen': 'ansi',
231
+ 'screen-256color': 'ansi',
232
+ 'tmux': 'ansi',
233
+ 'tmux-256color': 'ansi',
234
+ 'vt100': 'ansi',
235
+ 'vt220': 'ansi',
236
+ 'rxvt': 'ansi',
237
+ 'rxvt-unicode': 'ansi',
238
+ 'rxvt-unicode-256color': 'ansi',
239
+ 'unknown': 'dumb',
240
+ }
241
+
242
+
243
+ # Terminfo constants
244
+ _MAGIC16 = 0o432 # Magic number for 16-bit terminfo format
245
+ _MAGIC32 = 0o1036 # Magic number for 32-bit terminfo format
246
+
247
+
248
+ # Standard string capability names from ncurses Caps file This matches the order used by ncurses when compiling terminfo
249
+ _ORDERED_STRING_NAMES: tuple[str, ...] = (
250
+ 'cbt',
251
+ 'bel',
252
+ 'cr',
253
+ 'csr',
254
+ 'tbc',
255
+ 'clear',
256
+ 'el',
257
+ 'ed',
258
+ 'hpa',
259
+ 'cmdch',
260
+ 'cup',
261
+ 'cud1',
262
+ 'home',
263
+ 'civis',
264
+ 'cub1',
265
+ 'mrcup',
266
+ 'cnorm',
267
+ 'cuf1',
268
+ 'll',
269
+ 'cuu1',
270
+ 'cvvis',
271
+ 'dch1',
272
+ 'dl1',
273
+ 'dsl',
274
+ 'hd',
275
+ 'smacs',
276
+ 'blink',
277
+ 'bold',
278
+ 'smcup',
279
+ 'smdc',
280
+ 'dim',
281
+ 'smir',
282
+ 'invis',
283
+ 'prot',
284
+ 'rev',
285
+ 'smso',
286
+ 'smul',
287
+ 'ech',
288
+ 'rmacs',
289
+ 'sgr0',
290
+ 'rmcup',
291
+ 'rmdc',
292
+ 'rmir',
293
+ 'rmso',
294
+ 'rmul',
295
+ 'flash',
296
+ 'ff',
297
+ 'fsl',
298
+ 'is1',
299
+ 'is2',
300
+ 'is3',
301
+ 'if',
302
+ 'ich1',
303
+ 'il1',
304
+ 'ip',
305
+ 'kbs',
306
+ 'ktbc',
307
+ 'kclr',
308
+ 'kctab',
309
+ 'kdch1',
310
+ 'kdl1',
311
+ 'kcud1',
312
+ 'krmir',
313
+ 'kel',
314
+ 'ked',
315
+ 'kf0',
316
+ 'kf1',
317
+ 'kf10',
318
+ 'kf2',
319
+ 'kf3',
320
+ 'kf4',
321
+ 'kf5',
322
+ 'kf6',
323
+ 'kf7',
324
+ 'kf8',
325
+ 'kf9',
326
+ 'khome',
327
+ 'kich1',
328
+ 'kil1',
329
+ 'kcub1',
330
+ 'kll',
331
+ 'knp',
332
+ 'kpp',
333
+ 'kcuf1',
334
+ 'kind',
335
+ 'kri',
336
+ 'khts',
337
+ 'kcuu1',
338
+ 'rmkx',
339
+ 'smkx',
340
+ 'lf0',
341
+ 'lf1',
342
+ 'lf10',
343
+ 'lf2',
344
+ 'lf3',
345
+ 'lf4',
346
+ 'lf5',
347
+ 'lf6',
348
+ 'lf7',
349
+ 'lf8',
350
+ 'lf9',
351
+ 'rmm',
352
+ 'smm',
353
+ 'nel',
354
+ 'pad',
355
+ 'dch',
356
+ 'dl',
357
+ 'cud',
358
+ 'ich',
359
+ 'indn',
360
+ 'il',
361
+ 'cub',
362
+ 'cuf',
363
+ 'rin',
364
+ 'cuu',
365
+ 'pfkey',
366
+ 'pfloc',
367
+ 'pfx',
368
+ 'mc0',
369
+ 'mc4',
370
+ 'mc5',
371
+ 'rep',
372
+ 'rs1',
373
+ 'rs2',
374
+ 'rs3',
375
+ 'rf',
376
+ 'rc',
377
+ 'vpa',
378
+ 'sc',
379
+ 'ind',
380
+ 'ri',
381
+ 'sgr',
382
+ 'hts',
383
+ 'wind',
384
+ 'ht',
385
+ 'tsl',
386
+ 'uc',
387
+ 'hu',
388
+ 'iprog',
389
+ 'ka1',
390
+ 'ka3',
391
+ 'kb2',
392
+ 'kc1',
393
+ 'kc3',
394
+ 'mc5p',
395
+ 'rmp',
396
+ 'acsc',
397
+ 'pln',
398
+ 'kcbt',
399
+ 'smxon',
400
+ 'rmxon',
401
+ 'smam',
402
+ 'rmam',
403
+ 'xonc',
404
+ 'xoffc',
405
+ 'enacs',
406
+ 'smln',
407
+ 'rmln',
408
+ 'kbeg',
409
+ 'kcan',
410
+ 'kclo',
411
+ 'kcmd',
412
+ 'kcpy',
413
+ 'kcrt',
414
+ 'kend',
415
+ 'kent',
416
+ 'kext',
417
+ 'kfnd',
418
+ 'khlp',
419
+ 'kmrk',
420
+ 'kmsg',
421
+ 'kmov',
422
+ 'knxt',
423
+ 'kopn',
424
+ 'kopt',
425
+ 'kprv',
426
+ 'kprt',
427
+ 'krdo',
428
+ 'kref',
429
+ 'krfr',
430
+ 'krpl',
431
+ 'krst',
432
+ 'kres',
433
+ 'ksav',
434
+ 'kspd',
435
+ 'kund',
436
+ 'kBEG',
437
+ 'kCAN',
438
+ 'kCMD',
439
+ 'kCPY',
440
+ 'kCRT',
441
+ 'kDC',
442
+ 'kDL',
443
+ 'kslt',
444
+ 'kEND',
445
+ 'kEOL',
446
+ 'kEXT',
447
+ 'kFND',
448
+ 'kHLP',
449
+ 'kHOM',
450
+ 'kIC',
451
+ 'kLFT',
452
+ 'kMSG',
453
+ 'kMOV',
454
+ 'kNXT',
455
+ 'kOPT',
456
+ 'kPRV',
457
+ 'kPRT',
458
+ 'kRDO',
459
+ 'kRPL',
460
+ 'kRIT',
461
+ 'kRES',
462
+ 'kSAV',
463
+ 'kSPD',
464
+ 'kUND',
465
+ 'rfi',
466
+ 'kf11',
467
+ 'kf12',
468
+ 'kf13',
469
+ 'kf14',
470
+ 'kf15',
471
+ 'kf16',
472
+ 'kf17',
473
+ 'kf18',
474
+ 'kf19',
475
+ 'kf20',
476
+ 'kf21',
477
+ 'kf22',
478
+ 'kf23',
479
+ 'kf24',
480
+ 'kf25',
481
+ 'kf26',
482
+ 'kf27',
483
+ 'kf28',
484
+ 'kf29',
485
+ 'kf30',
486
+ 'kf31',
487
+ 'kf32',
488
+ 'kf33',
489
+ 'kf34',
490
+ 'kf35',
491
+ 'kf36',
492
+ 'kf37',
493
+ 'kf38',
494
+ 'kf39',
495
+ 'kf40',
496
+ 'kf41',
497
+ 'kf42',
498
+ 'kf43',
499
+ 'kf44',
500
+ 'kf45',
501
+ 'kf46',
502
+ 'kf47',
503
+ 'kf48',
504
+ 'kf49',
505
+ 'kf50',
506
+ 'kf51',
507
+ 'kf52',
508
+ 'kf53',
509
+ 'kf54',
510
+ 'kf55',
511
+ 'kf56',
512
+ 'kf57',
513
+ 'kf58',
514
+ 'kf59',
515
+ 'kf60',
516
+ 'kf61',
517
+ 'kf62',
518
+ 'kf63',
519
+ 'el1',
520
+ 'mgc',
521
+ 'smgl',
522
+ 'smgr',
523
+ 'fln',
524
+ 'sclk',
525
+ 'dclk',
526
+ 'rmclk',
527
+ 'cwin',
528
+ 'wingo',
529
+ 'hup',
530
+ 'dial',
531
+ 'qdial',
532
+ 'tone',
533
+ 'pulse',
534
+ 'hook',
535
+ 'pause',
536
+ 'wait',
537
+ 'u0',
538
+ 'u1',
539
+ 'u2',
540
+ 'u3',
541
+ 'u4',
542
+ 'u5',
543
+ 'u6',
544
+ 'u7',
545
+ 'u8',
546
+ 'u9',
547
+ 'op',
548
+ 'oc',
549
+ 'initc',
550
+ 'initp',
551
+ 'scp',
552
+ 'setf',
553
+ 'setb',
554
+ 'cpi',
555
+ 'lpi',
556
+ 'chr',
557
+ 'cvr',
558
+ 'defc',
559
+ 'swidm',
560
+ 'sdrfq',
561
+ 'sitm',
562
+ 'slm',
563
+ 'smicm',
564
+ 'snlq',
565
+ 'snrmq',
566
+ 'sshm',
567
+ 'ssubm',
568
+ 'ssupm',
569
+ 'sum',
570
+ 'rwidm',
571
+ 'ritm',
572
+ 'rlm',
573
+ 'rmicm',
574
+ 'rshm',
575
+ 'rsubm',
576
+ 'rsupm',
577
+ 'rum',
578
+ 'mhpa',
579
+ 'mcud1',
580
+ 'mcub1',
581
+ 'mcuf1',
582
+ 'mvpa',
583
+ 'mcuu1',
584
+ 'porder',
585
+ 'mcud',
586
+ 'mcub',
587
+ 'mcuf',
588
+ 'mcuu',
589
+ 'scs',
590
+ 'smgb',
591
+ 'smgbp',
592
+ 'smglp',
593
+ 'smgrp',
594
+ 'smgt',
595
+ 'smgtp',
596
+ 'sbim',
597
+ 'scsd',
598
+ 'rbim',
599
+ 'rcsd',
600
+ 'subcs',
601
+ 'supcs',
602
+ 'docr',
603
+ 'zerom',
604
+ 'csnm',
605
+ 'kmous',
606
+ 'minfo',
607
+ 'reqmp',
608
+ 'getm',
609
+ 'setaf',
610
+ 'setab',
611
+ 'pfxl',
612
+ 'devt',
613
+ 'csin',
614
+ 's0ds',
615
+ 's1ds',
616
+ 's2ds',
617
+ 's3ds',
618
+ 'smglr',
619
+ 'smgtb',
620
+ 'birep',
621
+ 'binel',
622
+ 'bicr',
623
+ 'colornm',
624
+ 'defbi',
625
+ 'endbi',
626
+ 'setcolor',
627
+ 'slines',
628
+ 'dispc',
629
+ 'smpch',
630
+ 'rmpch',
631
+ 'smsc',
632
+ 'rmsc',
633
+ 'pctrm',
634
+ 'scesc',
635
+ 'scesa',
636
+ 'ehhlm',
637
+ 'elhlm',
638
+ 'elohlm',
639
+ 'erhlm',
640
+ 'ethlm',
641
+ 'evhlm',
642
+ 'sgr1',
643
+ 'slength',
644
+ 'OTi2',
645
+ 'OTrs',
646
+ 'OTnl',
647
+ 'OTbc',
648
+ 'OTko',
649
+ 'OTma',
650
+ 'OTG2',
651
+ 'OTG3',
652
+ 'OTG1',
653
+ 'OTG4',
654
+ 'OTGR',
655
+ 'OTGL',
656
+ 'OTGU',
657
+ 'OTGD',
658
+ 'OTGH',
659
+ 'OTGV',
660
+ 'OTGC',
661
+ 'meml',
662
+ 'memu',
663
+ 'box1',
664
+ )
665
+
666
+
667
+ def _get_terminfo_dirs() -> list[pathlib.Path]:
668
+ """
669
+ Get list of directories to search for terminfo files.
670
+
671
+ Based on ncurses behavior in:
672
+ - ncurses/tinfo/db_iterator.c:_nc_next_db()
673
+ - ncurses/tinfo/read_entry.c:_nc_read_entry()
674
+ """
675
+
676
+ dirs = []
677
+
678
+ terminfo = os.environ.get('TERMINFO')
679
+ if terminfo:
680
+ dirs.append(terminfo)
681
+
682
+ try:
683
+ home = pathlib.Path.home()
684
+ dirs.append(str(home / '.terminfo'))
685
+ except RuntimeError:
686
+ pass
687
+
688
+ # Check TERMINFO_DIRS
689
+ terminfo_dirs = os.environ.get('TERMINFO_DIRS', '')
690
+ if terminfo_dirs:
691
+ for d in terminfo_dirs.split(':'):
692
+ if d:
693
+ dirs.append(d)
694
+
695
+ dirs.extend([
696
+ '/etc/terminfo',
697
+ '/lib/terminfo',
698
+ '/usr/lib/terminfo',
699
+ '/usr/share/terminfo',
700
+ '/usr/share/lib/terminfo',
701
+ '/usr/share/misc/terminfo',
702
+ '/usr/local/lib/terminfo',
703
+ '/usr/local/share/terminfo',
704
+ ])
705
+
706
+ return [pathlib.Path(d) for d in dirs if pathlib.Path(d).is_dir()]
707
+
708
+
709
+ def _validate_terminal_name_or_raise(terminal_name: str) -> None:
710
+ if not isinstance(terminal_name, str):
711
+ raise TypeError('`terminal_name` must be a string')
712
+
713
+ if not terminal_name:
714
+ raise ValueError('`terminal_name` cannot be empty')
715
+
716
+ if '\x00' in terminal_name:
717
+ raise ValueError('NUL character found in `terminal_name`')
718
+
719
+ t = pathlib.Path(terminal_name)
720
+ if len(t.parts) > 1:
721
+ raise ValueError('`terminal_name` cannot contain path separators')
722
+
723
+
724
+ def _read_terminfo_file(terminal_name: str) -> bytes:
725
+ """
726
+ Find and read terminfo file for given terminal name.
727
+
728
+ Terminfo files are stored in directories using the first character of the terminal name as a subdirectory.
729
+ """
730
+
731
+ _validate_terminal_name_or_raise(terminal_name)
732
+ first_char = terminal_name[0].lower()
733
+ filename = terminal_name
734
+
735
+ for directory in _get_terminfo_dirs():
736
+ path = directory / first_char / filename
737
+ if path.is_file():
738
+ return path.read_bytes()
739
+
740
+ # Try with hex encoding of first char (for special chars)
741
+ hex_dir = f'{ord(first_char):02x}'
742
+ path = directory / hex_dir / filename
743
+ if path.is_file():
744
+ return path.read_bytes()
745
+
746
+ raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), filename)
747
+
748
+
749
+ def _parse_terminfo_file(terminal_name: str) -> ta.Mapping[str, bytes]:
750
+ """
751
+ Parse a terminfo file.
752
+
753
+ Populate the _capabilities dict for easy retrieval
754
+
755
+ Based on ncurses implementation in:
756
+ - ncurses/tinfo/read_entry.c:_nc_read_termtype()
757
+ - ncurses/tinfo/read_entry.c:_nc_read_file_entry()
758
+ - ncurses/tinfo/lib_ti.c:tigetstr()
759
+ """
760
+
761
+ data = _read_terminfo_file(terminal_name)
762
+ too_short = f'TermInfo file for {terminal_name!r} too short'
763
+ offset = 12
764
+ if len(data) < offset:
765
+ raise ValueError(too_short)
766
+
767
+ magic, name_size, bool_count, num_count, str_count, str_size = \
768
+ struct.unpack('<Hhhhhh', data[:offset])
769
+
770
+ if magic == _MAGIC16:
771
+ number_size = 2
772
+ elif magic == _MAGIC32:
773
+ number_size = 4
774
+ else:
775
+ raise ValueError(f'TermInfo file for {terminal_name!r} uses unknown magic')
776
+
777
+ # Skip data than PyREPL doesn't need:
778
+ # - names (`|`-separated ASCII strings)
779
+ # - boolean capabilities (bytes with value 0 or 1)
780
+ # - numbers (little-endian integers, `number_size` bytes each)
781
+ offset += name_size
782
+ offset += bool_count
783
+ if offset % 2:
784
+ # Align to even byte boundary for numbers
785
+ offset += 1
786
+ offset += num_count * number_size
787
+ if offset > len(data):
788
+ raise ValueError(too_short)
789
+
790
+ # Read string offsets
791
+ end_offset = offset + 2 * str_count
792
+ if offset > len(data):
793
+ raise ValueError(too_short)
794
+ string_offset_data = data[offset:end_offset]
795
+ string_offsets = [off for [off] in struct.iter_unpack('<h', string_offset_data)]
796
+ offset = end_offset
797
+
798
+ # Read string table
799
+ if offset + str_size > len(data):
800
+ raise ValueError(too_short)
801
+ string_table = data[offset: offset + str_size]
802
+
803
+ # Extract strings from string table
804
+ capabilities = {}
805
+ for cap, off in zip(_ORDERED_STRING_NAMES, string_offsets):
806
+ if off < 0:
807
+ # CANCELLED_STRING; we do not store those
808
+ continue
809
+ elif off < len(string_table):
810
+ # Find null terminator
811
+ end = string_table.find(0, off)
812
+ if end >= 0:
813
+ capabilities[cap] = string_table[off:end]
814
+ # in other cases this is ABSENT_STRING; we don't store those.
815
+
816
+ # Note: we don't support extended capabilities since PyREPL doesn't need them.
817
+
818
+ return capabilities
819
+
820
+
821
+ ##
822
+
823
+
824
+ @dc.dataclass(frozen=True)
825
+ class TermInfo:
826
+ terminal_name: str
827
+ capabilities: ta.Mapping[str, bytes]
828
+
829
+ is_fallback: bool = False
830
+
831
+ def get(self, cap: str) -> bytes | None:
832
+ """Get terminal capability string by name."""
833
+
834
+ if not isinstance(cap, str):
835
+ raise TypeError(f'`cap` must be a string, not {type(cap)}')
836
+
837
+ return self.capabilities.get(cap)
838
+
839
+
840
+ def load_term_info(
841
+ terminal_name: str | bytes | None,
842
+ *,
843
+ fallback: bool = True,
844
+ ) -> TermInfo:
845
+ """
846
+ Initialize terminal capabilities for the given terminal type.
847
+
848
+ Based on ncurses implementation in:
849
+ - ncurses/tinfo/lib_setup.c:setupterm() and _nc_setupterm()
850
+ - ncurses/tinfo/lib_setup.c:TINFO_SETUP_TERM()
851
+
852
+ This version first attempts to read terminfo database files like ncurses, then, if `fallback` is True, falls
853
+ back to hardcoded capabilities for common terminal types.
854
+ """
855
+
856
+ # If termstr is None or empty, try to get from environment
857
+ if not terminal_name:
858
+ terminal_name = os.environ.get('TERM') or 'ANSI'
859
+
860
+ if isinstance(terminal_name, bytes):
861
+ terminal_name = terminal_name.decode('ascii')
862
+
863
+ is_fallback = False
864
+ try:
865
+ capabilities = _parse_terminfo_file(terminal_name)
866
+ except (OSError, ValueError):
867
+ if not fallback:
868
+ raise
869
+
870
+ term_type = _TERM_ALIASES.get(terminal_name, terminal_name)
871
+ if term_type not in _FALLBACK_TERMINAL_CAPABILITIES:
872
+ term_type = 'dumb'
873
+
874
+ is_fallback = True
875
+ capabilities = _FALLBACK_TERMINAL_CAPABILITIES[term_type]
876
+
877
+ return TermInfo(
878
+ terminal_name=terminal_name,
879
+ capabilities=capabilities,
880
+ is_fallback=is_fallback,
881
+ )
882
+
883
+
884
+ ##
885
+
886
+
887
+ def tparm(cap_bytes: bytes, *params: int) -> bytes:
888
+ """
889
+ Parameterize a terminal capability string.
890
+
891
+ Based on ncurses implementation in:
892
+ - ncurses/tinfo/lib_tparm.c:tparm()
893
+ - ncurses/tinfo/lib_tparm.c:tparam_internal()
894
+
895
+ The ncurses version implements a full stack-based interpreter for terminfo parameter strings. This pure Python
896
+ version implements only the subset of parameter substitution operations needed by PyREPL:
897
+ - %i (increment parameters for 1-based indexing)
898
+ - %p[1-9]%d (parameter substitution)
899
+ - %p[1-9]%{n}%+%d (parameter plus constant)
900
+ """
901
+
902
+ if not isinstance(cap_bytes, bytes):
903
+ raise TypeError(f'`cap` must be bytes, not {type(cap_bytes)}')
904
+
905
+ result = cap_bytes
906
+
907
+ # %i - increment parameters (1-based instead of 0-based)
908
+ increment = b'%i' in result
909
+ if increment:
910
+ result = result.replace(b'%i', b'')
911
+
912
+ # Replace %p1%d, %p2%d, etc. with actual parameter values
913
+ for i in range(len(params)):
914
+ pattern = b'%%p%d%%d' % (i + 1)
915
+ if pattern in result:
916
+ value = params[i]
917
+ if increment:
918
+ value += 1
919
+ result = result.replace(pattern, str(value).encode('ascii'))
920
+
921
+ # Handle %p1%{1}%+%d (parameter plus constant)
922
+ # Used in some cursor positioning sequences
923
+ pattern_re = re.compile(rb'%p(\d)%\{(\d+)\}%\+%d')
924
+ matches = list(pattern_re.finditer(result))
925
+ for match in reversed(matches): # reversed to maintain positions
926
+ param_idx = int(match.group(1))
927
+ constant = int(match.group(2))
928
+ value = params[param_idx] + constant
929
+ result = (
930
+ result[: match.start()] +
931
+ str(value).encode('ascii') +
932
+ result[match.end():]
933
+ )
934
+
935
+ return result