reykit 1.0.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.
reykit/rtext.py ADDED
@@ -0,0 +1,458 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-08 13:18:24
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Text methods.
9
+ """
10
+
11
+
12
+ from typing import Any, Literal, Optional
13
+ from collections.abc import Iterable
14
+ from decimal import Decimal
15
+ from pprint import pformat as pprint_pformat
16
+ from json import dumps as json_dumps
17
+
18
+ from .rexception import throw
19
+ from .rmonkey import monkey_patch_pprint_modify_width_judgment
20
+ from .rrandom import randi
21
+
22
+
23
+ __all__ = (
24
+ 'split_text',
25
+ 'get_width',
26
+ 'fill_width',
27
+ 'join_data_text',
28
+ 'join_filter_text',
29
+ 'add_text_frame',
30
+ 'to_json',
31
+ 'to_text'
32
+ )
33
+
34
+
35
+ # Monkey path.
36
+ monkey_patch_pprint_modify_width_judgment()
37
+
38
+
39
+ def split_text(text: str, man_len: int, by_width: bool = False) -> list[str]:
40
+ """
41
+ Split text by max length or not greater than display width.
42
+
43
+ Parameters
44
+ ----------
45
+ text : Text.
46
+ man_len : max length.
47
+ by_width : Whether by char displayed width count length.
48
+
49
+ Returns
50
+ -------
51
+ Split text.
52
+ """
53
+
54
+ # Split.
55
+ texts = []
56
+
57
+ ## By char displayed width.
58
+ if by_width:
59
+ str_group = []
60
+ str_width = 0
61
+ for char in text:
62
+ char_width = get_width(char)
63
+ str_width += char_width
64
+ if str_width > man_len:
65
+ string = ''.join(str_group)
66
+ texts.append(string)
67
+ str_group = [char]
68
+ str_width = char_width
69
+ else:
70
+ str_group.append(char)
71
+ string = ''.join(str_group)
72
+ texts.append(string)
73
+
74
+ ## By char number.
75
+ else:
76
+ test_len = len(text)
77
+ split_n = test_len // man_len
78
+ if test_len % man_len:
79
+ split_n += 1
80
+ for n in range(split_n):
81
+ start_indxe = man_len * n
82
+ end_index = man_len * (n + 1)
83
+ text_group = text[start_indxe:end_index]
84
+ texts.append(text_group)
85
+
86
+ return texts
87
+
88
+
89
+ def get_width(text: str) -> int:
90
+ """
91
+ Get text display width.
92
+
93
+ Parameters
94
+ ----------
95
+ text : Text.
96
+
97
+ Returns
98
+ -------
99
+ Text display width.
100
+ """
101
+
102
+ # Set parameter.
103
+ widths = (
104
+ (126, 1),
105
+ (159, 0),
106
+ (687, 1),
107
+ (710, 0),
108
+ (711, 1),
109
+ (727, 0),
110
+ (733, 1),
111
+ (879, 0),
112
+ (1154, 1),
113
+ (1161, 0),
114
+ (4347, 1),
115
+ (4447, 2),
116
+ (7467, 1),
117
+ (7521, 0),
118
+ (8369, 1),
119
+ (8426, 0),
120
+ (9000, 1),
121
+ (9002, 2),
122
+ (11021, 1),
123
+ (12350, 2),
124
+ (12351, 1),
125
+ (12438, 2),
126
+ (12442, 0),
127
+ (19893, 2),
128
+ (19967, 1),
129
+ (55203, 2),
130
+ (63743, 1),
131
+ (64106, 2),
132
+ (65039, 1),
133
+ (65059, 0),
134
+ (65131, 2),
135
+ (65279, 1),
136
+ (65376, 2),
137
+ (65500, 1),
138
+ (65510, 2),
139
+ (120831, 1),
140
+ (262141, 2),
141
+ (1114109, 1)
142
+ )
143
+
144
+ # Get width.
145
+ total_width = 0
146
+ for char in text:
147
+ char_unicode = ord(char)
148
+ if (
149
+ char_unicode == 0xe
150
+ or char_unicode == 0xf
151
+ ):
152
+ char_width = 0
153
+ else:
154
+ char_width = 1
155
+ for num, wid in widths:
156
+ if char_unicode <= num:
157
+ char_width = wid
158
+ break
159
+ total_width += char_width
160
+
161
+ return total_width
162
+
163
+
164
+ def fill_width(text: str, char: str, width: int, align: Literal['left', 'right', 'center'] = 'right') -> str:
165
+ """
166
+ Text fill character by display width.
167
+
168
+ Parameters
169
+ ----------
170
+ text : Fill text.
171
+ char : Fill character.
172
+ width : Fill width.
173
+ align : Align orientation.
174
+ - `Literal[`left`]`: Fill right, align left.
175
+ - `Literal[`right`]`: Fill left, align right.
176
+ - `Literal[`center`]`: Fill both sides, align center.
177
+
178
+ Returns
179
+ -------
180
+ Text after fill.
181
+ """
182
+
183
+ # Check parameter.
184
+ if get_width(char) != 1:
185
+ throw(ValueError, char)
186
+
187
+ # Fill width.
188
+ text_width = get_width(text)
189
+ fill_width = width - text_width
190
+ if fill_width > 0:
191
+ match align:
192
+ case 'left':
193
+ new_text = ''.join((char * fill_width, text))
194
+ case 'right':
195
+ new_text = ''.join((text, char * fill_width))
196
+ case 'center':
197
+ fill_width_left = int(fill_width / 2)
198
+ fill_width_right = fill_width - fill_width_left
199
+ new_text = ''.join((char * fill_width_left, text, char * fill_width_right))
200
+ case _:
201
+ throw(ValueError, align)
202
+ else:
203
+ new_text = text
204
+
205
+ return new_text
206
+
207
+
208
+ def join_data_text(data: Iterable) -> str:
209
+ """
210
+ Join data to text.
211
+
212
+ Parameters
213
+ ----------
214
+ data : Data.
215
+
216
+ Returns
217
+ -------
218
+ Joined text.
219
+ """
220
+
221
+ # Join.
222
+
223
+ ## dict type.
224
+ if data.__class__ == dict:
225
+ texts = []
226
+ for key, value in data.items():
227
+ key_str = str(key)
228
+ value_str = str(value)
229
+ if '\n' in value_str:
230
+ value_str = value_str.replace('\n', '\n ')
231
+ text_part = f'{key_str}:\n {value_str}'
232
+ else:
233
+ text_part = f'{key_str}: {value_str}'
234
+ texts.append(text_part)
235
+ text = '\n'.join(texts)
236
+
237
+ ## Other type.
238
+ else:
239
+ text = '\n'.join(
240
+ [
241
+ str(element)
242
+ for element in data
243
+ ]
244
+ )
245
+
246
+ return text
247
+
248
+
249
+ def join_filter_text(data: Iterable, char: str = ',', filter_: tuple = (None, '')) -> str:
250
+ """
251
+ Join and filter text.
252
+
253
+ Parameters
254
+ ----------
255
+ data : Data.
256
+ - `Element is 'str'`: Join.
257
+ - `Element is 'Any'`: Convert to string and join.
258
+ char : Join character.
259
+ filter_ : Filter elements.
260
+
261
+ Returns
262
+ -------
263
+ Joined text.
264
+ """
265
+
266
+ # Filter and convert.
267
+ data = [
268
+ str(element)
269
+ for element in data
270
+ if element not in filter_
271
+ ]
272
+
273
+ # Join.
274
+ text = char.join(data)
275
+
276
+ return text
277
+
278
+
279
+ def add_text_frame(
280
+ *texts: str,
281
+ title: Optional[str],
282
+ width: int,
283
+ frame: Literal['full', 'half', 'top', 'half_plain', 'top_plain']
284
+ ) -> str:
285
+ """
286
+ Add text frame.
287
+
288
+ Parameters
289
+ ----------
290
+ texts : Texts.
291
+ title : Frame title.
292
+ - `Union[None, Literal['']]`: No title.
293
+ - `str`: Use this value as the title.
294
+ width : Frame width.
295
+ frame : Frame type.
296
+ - `Literal[`full`]`: Add beautiful four side frame and limit length.
297
+ When throw `exception`, then frame is `half` type.
298
+ - `Literal[`half`]`: Add beautiful top and bottom side frame.
299
+ - `Literal[`top`]`: Add beautiful top side frame.
300
+ - `Literal[`half_plain`]`: Add plain top and bottom side frame.
301
+ - `Literal[`top_plain`]`: Add plain top side frame.
302
+
303
+ Returns
304
+ -------
305
+ Added frame text.
306
+ """
307
+
308
+ # Handle parameter.
309
+ if title is None or len(title) > width - 6:
310
+ title = ''
311
+
312
+ # Generate frame.
313
+ match frame:
314
+
315
+ ## Full type.
316
+ case 'full':
317
+ if title != '':
318
+ title = f'╡ {title} ╞'
319
+ width_in = width - 2
320
+ _contents = []
321
+ try:
322
+ for content in texts:
323
+ content_str = str(content)
324
+ pieces_str = content_str.split('\n')
325
+ content_str = [
326
+ '║%s║' % fill_width(line_str, ' ', width_in)
327
+ for piece_str in pieces_str
328
+ for line_str in split_text(piece_str, width_in, True)
329
+ ]
330
+ content = '\n'.join(content_str)
331
+ _contents.append(content)
332
+ except:
333
+ frame_top = fill_width(title, '═', width, 'center')
334
+ frame_split = '─' * width
335
+ frame_bottom = '═' * width
336
+ _contents = texts
337
+ else:
338
+ frame_top = '╔%s╗' % fill_width(title, '═', width_in, 'center')
339
+ frame_split = '╟%s╢' % ('─' * width_in)
340
+ frame_bottom = '╚%s╝' % ('═' * width_in)
341
+
342
+ ## Half type.
343
+ case 'half' | 'top':
344
+ if title != '':
345
+ title = f'╡ {title} ╞'
346
+ frame_top = fill_width(title, '═', width, 'center')
347
+ frame_split = '─' * width
348
+ match frame:
349
+ case 'half':
350
+ frame_bottom = '═' * width
351
+ case 'top':
352
+ frame_bottom = None
353
+ _contents = texts
354
+
355
+ ## Plain type.
356
+ case 'half_plain' | 'top_plain':
357
+ if title != '':
358
+ title = f'| {title} |'
359
+ frame_top = fill_width(title, '=', width, 'center')
360
+ frame_split = '-' * width
361
+ match frame:
362
+ case 'half_plain':
363
+ frame_bottom = '=' * width
364
+ case 'top_plain':
365
+ frame_bottom = None
366
+ _contents = texts
367
+
368
+ ## Throw exception.
369
+ case _:
370
+ throw(ValueError, frame)
371
+
372
+ # Join.
373
+ texts = [frame_top]
374
+ for index, content in enumerate(_contents):
375
+ if index != 0:
376
+ texts.append(frame_split)
377
+ texts.append(content)
378
+ texts.append(frame_bottom)
379
+ text = join_filter_text(texts, '\n')
380
+
381
+ return text
382
+
383
+
384
+ def to_json(
385
+ data: Any,
386
+ compact: bool = True
387
+ ) -> str:
388
+ """
389
+ Convert data to JSON format string.
390
+
391
+ Parameters
392
+ ----------
393
+ data : Data.
394
+ compact : Whether compact content.
395
+
396
+ Returns
397
+ -------
398
+ JSON format string.
399
+ """
400
+
401
+ # Get parameter.
402
+ if compact:
403
+ indent = None
404
+ separators = (',', ':')
405
+ else:
406
+ indent = 4
407
+ separators = None
408
+
409
+ # Convert.
410
+ default = lambda value: (
411
+ value.__float__()
412
+ if value.__class__ == Decimal
413
+ else repr(value)
414
+ )
415
+ string = json_dumps(
416
+ data,
417
+ ensure_ascii=False,
418
+ indent=indent,
419
+ separators=separators,
420
+ default=default
421
+ )
422
+
423
+ return string
424
+
425
+
426
+ def to_text(
427
+ data: Any,
428
+ width: int = 100
429
+ ) -> str:
430
+ """
431
+ Format data to text.
432
+
433
+ Parameters
434
+ ----------
435
+ data : Data.
436
+ width : Format width.
437
+
438
+ Returns
439
+ -------
440
+ Formatted text.
441
+ """
442
+
443
+ # Format.
444
+ match data:
445
+
446
+ ## Replace tab.
447
+ case str():
448
+ text = data.replace('\t', ' ')
449
+
450
+ ## Format contents.
451
+ case list() | tuple() | dict() | set():
452
+ text = pprint_pformat(data, width=width, sort_dicts=False)
453
+
454
+ ## Other.
455
+ case _:
456
+ text = str(data)
457
+
458
+ return text