convert-stream 1.1__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 convert-stream might be problematic. Click here for more details.

@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ from __future__ import annotations
4
+ from typing import List, Dict
5
+ from datetime import datetime
6
+ from enum import Enum
7
+ from pandas import DataFrame, Timestamp
8
+ import re
9
+ import os
10
+
11
+ from soup_files import File
12
+
13
+ def print_line(char:str='='):
14
+ """
15
+ Imprime uma linha na tela.
16
+ """
17
+ try:
18
+ num_size = os.get_terminal_size().columns
19
+ except:
20
+ num_size = 80
21
+ print(char * num_size)
22
+
23
+
24
+ def print_title(title:str, char:str='='):
25
+ """
26
+ Imprime um título na tela.
27
+ """
28
+ try:
29
+ num_size = os.get_terminal_size().columns
30
+ except:
31
+ num_size = 80
32
+
33
+ #print_line(char)
34
+ print(title.center(num_size, char))
35
+
36
+
37
+
38
+ class LibraryDates(Enum):
39
+ D_M_Y = '%d-%m-%Y'
40
+ DMY = '%d/%m/%Y'
41
+ dmy = '%d/%m/%y'
42
+ d_m_y = '%d-%m-%y'
43
+ YMD = '%Y/%m/%d'
44
+ Y_M_D = '%Y-%m-%d'
45
+
46
+ def is_valid_date(d) -> bool:
47
+ valid_formats = (
48
+ '%d-%m-%Y', # Exemplo: 11-01-2025
49
+ '%d/%m/%y',
50
+ '%d-%m-%y',
51
+ '%Y/%m/%d', # Exemplo: 2025/01/11
52
+ '%d/%m/%Y', # Exemplo: 11/01/2025
53
+ '%Y-%m-%d', # Exemplo: 2025-01-11
54
+ '%d %B %Y', # Exemplo: 11 Janeiro 2025
55
+ '%b %d, %Y', # Exemplo: Jan 11, 2025
56
+ '%A, %d %B %Y', # Exemplo: Sábado, 11 Janeiro 2025
57
+ '%H:%M:%S', # Exemplo: 08:35:00
58
+ '%H:%M', # Exemplo: 08:35
59
+ '%I:%M %p', # Exemplo: 08:35 AM
60
+ '%Y-%m-%d %H:%M:%S', # Exemplo: 2025-01-11 08:35:00
61
+ '%Y-%m-%dT%H:%M:%S', # Exemplo: 2025-01-11T08:35:00 (Formato ISO 8601)
62
+ '%Y%m%dT%H%M%S', # Exemplo: 20250111T083500 (Formato compactado)
63
+ )
64
+ #
65
+
66
+ _valid = False
67
+ for fmt in valid_formats:
68
+ try:
69
+ datetime.strptime(d, fmt)
70
+ _valid = True
71
+ break
72
+ except ValueError:
73
+ pass
74
+ #
75
+ return _valid
76
+
77
+ def save_data(df:DataFrame, file:File):
78
+ try:
79
+ df.to_excel(file.absolute(), index=False)
80
+ except Exception as e:
81
+ print(e)
82
+ else:
83
+ print('OK')
84
+
85
+ class ConvertDate(object):
86
+ def __init__(self):
87
+ """
88
+ Converter vários formatos de datas.
89
+ """
90
+ self.valid_formats = (
91
+ '%d-%m-%Y', # Exemplo: 11-01-2025
92
+ '%d/%m/%y',
93
+ '%d-%m-%y',
94
+ '%Y/%m/%d', # Exemplo: 2025/01/11
95
+ '%d/%m/%Y', # Exemplo: 11/01/2025
96
+ '%Y-%m-%d', # Exemplo: 2025-01-11
97
+ '%d %B %Y', # Exemplo: 11 Janeiro 2025
98
+ '%b %d, %Y', # Exemplo: Jan 11, 2025
99
+ '%A, %d %B %Y', # Exemplo: Sábado, 11 Janeiro 2025
100
+ '%H:%M:%S', # Exemplo: 08:35:00
101
+ '%H:%M', # Exemplo: 08:35
102
+ '%I:%M %p', # Exemplo: 08:35 AM
103
+ '%Y-%m-%d %H:%M:%S', # Exemplo: 2025-01-11 08:35:00
104
+ '%Y-%m-%dT%H:%M:%S', # Exemplo: 2025-01-11T08:35:00 (Formato ISO 8601)
105
+ '%Y%m%dT%H%M%S', # Exemplo: 20250111T083500 (Formato compactado)
106
+ "%Y-%m-%d %H:%M:%S",
107
+ "%Y-%m-%d %H:%M:%S.%f",
108
+ "%Y-%m-%dT%H:%M:%S", # ISO-like
109
+ "%Y-%m-%dT%H:%M:%S.%f"
110
+ )
111
+ self.valid_timestamp = (
112
+ '%Y-%m-%d %H:%M:%S', # Exemplo: 2025-01-11 08:35:00
113
+ '%Y-%m-%dT%H:%M:%S', # Exemplo: 2025-01-11T08:35:00 (Formato ISO 8601)
114
+ '%Y%m%dT%H%M%S', # Exemplo: 20250111T083500 (Formato compactado)
115
+ "%Y-%m-%d %H:%M:%S",
116
+ "%Y-%m-%dT%H:%M:%S", # ISO-like
117
+ "%Y-%m-%d %H:%M:%S.%f",
118
+ "%Y-%m-%dT%H:%M:%S.%f"
119
+ )
120
+ #
121
+ # Dicionário de mapeamento de nomes de meses em português para inglês
122
+ self.meses_para_ingles = {
123
+ 'Janeiro': 'January',
124
+ 'Fevereiro': 'February',
125
+ 'Março': 'March',
126
+ 'Abril': 'April',
127
+ 'Maio': 'May',
128
+ 'Junho': 'June',
129
+ 'Julho': 'July',
130
+ 'Agosto': 'August',
131
+ 'Setembro': 'September',
132
+ 'Outubro': 'October',
133
+ 'Novembro': 'November',
134
+ 'Dezembro': 'December',
135
+ }
136
+
137
+ # Mapeamento dos meses em português para números
138
+ self.meses_ptbr_para_numeros = {
139
+ "janeiro": "01", "fevereiro": "02", "março": "03", "abril": "04",
140
+ "maio": "05", "junho": "06", "julho": "07", "agosto": "08",
141
+ "setembro": "09", "outubro": "10", "novembro": "11", "dezembro": "12"
142
+ }
143
+
144
+ def is_valid_date(self, d:str) -> bool:
145
+ for fmt in self.valid_formats:
146
+ try:
147
+ datetime.strptime(d, fmt)
148
+ return True
149
+ except:
150
+ pass
151
+
152
+ if ',' in d:
153
+ try:
154
+ d = self._long_date_ptbr_to_eng(d)
155
+ except:
156
+ pass
157
+ else:
158
+ return True
159
+ if self.is_timestamp(d):
160
+ return True
161
+ if self.convert_string_date_ptbr(d) is not None:
162
+ return True
163
+ return False
164
+
165
+ def is_timestamp(self, d:object) -> bool:
166
+ """
167
+ Verifica se o objeto atual é timestamp
168
+ """
169
+ if isinstance(d, Timestamp):
170
+ return True
171
+ for current_format in self.valid_timestamp:
172
+ try:
173
+ datetime.strptime(d, current_format)
174
+ return True
175
+ except:
176
+ pass
177
+ return False
178
+
179
+ def _long_date_ptbr_to_eng(self, data_string:str, fmt:str='%A, %d %B %Y') -> str:
180
+
181
+ # Exemplo de data em string com o dia da semana em português
182
+ data_string = 'Sábado, 11 Janeiro 2025'
183
+
184
+ # Remover o dia da semana da string
185
+ new_string_date = data_string.split(", ")[1]
186
+
187
+ # Formato da data sem o dia da semana
188
+ current_data_fmt = '%d %B %Y'
189
+
190
+ # Substituir o nome do mês em português pelo nome em inglês
191
+ for pt_mes, en_mes in self.meses_para_ingles.items():
192
+ new_string_date = new_string_date.replace(pt_mes, en_mes)
193
+
194
+ # Converter a string para um objeto datetime
195
+ to_datetime = datetime.strptime(new_string_date, current_data_fmt)
196
+
197
+ # Formatando o objeto datetime para o formato desejado "dia/mês/ANO"
198
+ return to_datetime.strftime(fmt)
199
+
200
+ def convert_string_date_ptbr(self, data_str_ptbr:str, *, fmt:LibraryDates=LibraryDates.DMY) -> str | None:
201
+ # Divide a string e substitui o mês pelo número correspondente
202
+ try:
203
+ dia, mes, ano = data_str_ptbr.split("-")
204
+ mes_num = self.meses_ptbr_para_numeros[mes.lower()] # Converte o mês para número
205
+ data_formatada = f"{dia}/{mes_num}/{ano}" # Formata para o padrão dd/mm/yyyy
206
+ # Converte para um objeto datetime
207
+ return datetime.strptime(data_formatada, fmt.value).strftime(fmt.value) # "%d/%m/%Y" ...
208
+ except Exception as e:
209
+ print(e)
210
+ return None
211
+
212
+ def convert_date(self, date:str, *, fmt:LibraryDates=LibraryDates.DMY) -> str | None:
213
+ """
214
+ Converter uma data em string para um formato qualquer.
215
+ """
216
+ if not self.is_valid_date(date):
217
+ return None
218
+ if ',' in date:
219
+ date = self._long_date_ptbr_to_eng(date)
220
+
221
+ for current_format in self.valid_formats:
222
+ try:
223
+ date_obj = datetime.strptime(date, current_format)
224
+ except:
225
+ pass
226
+ else:
227
+ return date_obj.strftime('{}'.format(fmt.value))
228
+ return self.convert_string_date_ptbr(date, fmt=fmt)
229
+
230
+ def convert_timestamp(self, ts:str, *, fmt:LibraryDates=LibraryDates.DMY) -> str | None:
231
+ """
232
+ Converter uma data em string para um formato qualquer.
233
+ """
234
+ if not self.is_timestamp(ts):
235
+ return None
236
+ for current_format in self.valid_timestamp:
237
+ try:
238
+ date_timestamp:datetime = datetime.strptime(ts, current_format)
239
+ except:
240
+ pass
241
+ else:
242
+ # Converter datetime para string no formato recebido via parâmetro.
243
+ return date_timestamp.strftime(fmt.value)
244
+ return None
245
+
246
+
247
+
248
+ class DataString(object):
249
+ def __init__(self, value:str):
250
+ self.value = value
251
+
252
+ def is_null(self) -> bool:
253
+ if (self.value is None) or (self.value == ''):
254
+ return True
255
+ return False
256
+
257
+ def to_utf8(self) -> DataString:
258
+ items_for_remove = [
259
+ '\xa0T\x04',
260
+ ]
261
+ try:
262
+ for i in items_for_remove:
263
+ REG = re.compile(i)
264
+ self.value = REG.sub("_", self.value)
265
+ except:
266
+ return self
267
+ else:
268
+ self.value = self.value.encode("utf-8", errors="replace").decode("utf-8")
269
+ return self
270
+
271
+ def to_upper(self) -> DataString:
272
+ self.value = self.value.upper()
273
+ return self
274
+
275
+ def to_list(self, separator:str=' ') -> List[str]:
276
+ """
277
+ Transforma uma string em uma lista de strings.
278
+ """
279
+ try:
280
+ return self.value.split(separator)
281
+ except Exception as e:
282
+ print(e)
283
+ return []
284
+
285
+ def to_array_string(self, separator:str=' ') -> ArrayString:
286
+ """
287
+ Transforma uma string em uma lista de strings.
288
+ """
289
+ try:
290
+ return ArrayString(self.value.split(separator))
291
+ except Exception as e:
292
+ print(e)
293
+ return ArrayString([])
294
+
295
+ def replace_all(self, char:str, new_char:str='_') -> DataString:
296
+ """
297
+ Usar expressão regular para substituir caracteres.
298
+ """
299
+ # re.sub(r'{}'.format(char), new_char, text)
300
+ self.value = re.sub(re.escape(char), new_char, self.value)
301
+ return self
302
+
303
+ def replace_bad_chars(self, *, new_char='-') -> DataString:
304
+ char_for_remove = [
305
+ ':', ',', ';', '$', '=',
306
+ '!', '}', '{', '(', ')',
307
+ '|', '\\', '‘', '*'
308
+ '¢', '“', '\'', '¢', '"',
309
+ '#', '<', '?', '>',
310
+ '»', '@', '+', '[', ']',
311
+ '%', '%', '~', '¥', '«',
312
+ '°', '¢', '”', '&'
313
+ ]
314
+
315
+ for char in char_for_remove:
316
+ self.replace_all(char, new_char)
317
+ format_chars = [
318
+ '-_', '_-', '--', '__',
319
+ ]
320
+ for c in format_chars:
321
+ self.replace_all(c)
322
+ return self
323
+
324
+
325
+ class ArrayString(object):
326
+ def __init__(self, values:List[str]):
327
+ self.values = values
328
+ self.length = len(self.values)
329
+
330
+ def add(self, item:str):
331
+ if not isinstance(item, str):
332
+ return
333
+ self.values.append(item)
334
+ self.length += 1
335
+
336
+ def add_all(self, items:List[str]):
337
+ for i in items:
338
+ self.add(i)
339
+
340
+ def for_each(self, cmd:callable):
341
+ for i in self.values:
342
+ cmd(i)
343
+
344
+ def get_next(self, text:str, iqual:bool=False) -> DataString:
345
+ """
346
+ Retorna a próxima string depois da ocorrência text.
347
+ """
348
+ _next_value = None
349
+ if iqual == True:
350
+ for num, i in enumerate(self.values):
351
+ if text == i:
352
+ if num+1 < self.length:
353
+ _next_value = self.values[num+1]
354
+ break
355
+ else:
356
+ for num, i in enumerate(self.values):
357
+ if text in i:
358
+ if num+1 < self.length:
359
+ _next_value = self.values[num+1]
360
+ break
361
+ if _next_value is None:
362
+ return DataString('')
363
+ return DataString(_next_value)
364
+
365
+ def get_back(self, text:str, iqual:bool=False) -> str:
366
+ new:List[str] = []
367
+ if iqual == True:
368
+ pass
369
+ else:
370
+ for item in self.values:
371
+ if text in item:
372
+ break
373
+ new.append(item)
374
+ return ', '.join(new)
375
+
376
+ def get_next_all(self, text:str, iqual:bool=False) -> ArrayString:
377
+ """
378
+ Retorna uma lista após a ocorrência text.
379
+ """
380
+ if iqual == True:
381
+ for num, i in enumerate(self.values):
382
+ if text == i:
383
+ if num+1 < self.length:
384
+ return ArrayString(self.values[num+1:])
385
+ else:
386
+ for num, i in enumerate(self.values):
387
+ if text in i:
388
+ if num+1 < self.length:
389
+ return ArrayString(self.values[num+1:])
390
+ return ArrayString([])
391
+
392
+ def get_back_all(self, text) -> ArrayString:
393
+ list_text:List[str] = []
394
+ for item in self.values:
395
+ if text in item:
396
+ break
397
+ list_text.append(item)
398
+ return ArrayString(list_text)
399
+
400
+ def get_contains(self, text:str) -> ArrayString:
401
+ """
402
+ Retorna uma lista de ocorrências que contém text.
403
+ """
404
+ items = []
405
+ for i in self.values:
406
+ if text in i:
407
+ items.append(i)
408
+ return ArrayString(items)
409
+
410
+ def contains(self, text:str) -> bool:
411
+ _contains = False
412
+ for item in self.values:
413
+ if text in item:
414
+ _contains = True
415
+ break
416
+ return _contains
417
+
418
+ def to_string(self, separator:str=' ') -> str:
419
+ try:
420
+ return separator.join(self.values)
421
+ except:
422
+ return None
423
+
424
+
425
+
426
+ # Tabela de arquivo/texto
427
+ class TableText(object):
428
+ def __init__(self, values:str, *, separator:str='\n', column_text:str='TEXT', file:File=None):
429
+ self.values:str = values
430
+ self.separator:str = separator
431
+ self.column_text:str = column_text
432
+ self.file:File = file
433
+
434
+ def get_len(self) -> int:
435
+ return len(self.to_list())
436
+
437
+ def is_null(self) -> bool:
438
+ if (self.values == None) or (self.values == ''):
439
+ return True
440
+ return False
441
+
442
+ def to_upper(self) -> None:
443
+ self.values = self.values.upper()
444
+
445
+ def to_list(self) -> List[str]:
446
+ try:
447
+ return self.values.split(self.separator)
448
+ except Exception as e:
449
+ print(e)
450
+ return []
451
+
452
+ def to_dict(self) -> Dict[str, List[str]]:
453
+ lines = self.to_list()
454
+ if len(lines) < 1:
455
+ return {}
456
+ tb:Dict[str, List[str]] = {}
457
+ tb[self.column_text] = []
458
+ tb['NUM_LINHA'] = []
459
+ for num, item in enumerate(lines):
460
+ tb['NUM_LINHA'].append(num+1)
461
+ tb[self.column_text].append(item)
462
+
463
+ max_num:int = len(lines)
464
+ if self.file is not None:
465
+ tb['NOME_ARQUIVO'] = [self.file.basename()] * max_num
466
+ tb['MD5'] = [self.file.md5()] * max_num
467
+ tb['EXTENSÃO'] = [self.file.extension()] * max_num
468
+ tb['ARQUIVO'] = [self.file.absolute()] * max_num
469
+ tb['PASTA'] = [self.file.dirname()] * max_num
470
+ return tb
471
+
472
+ def to_data(self) -> DataFrame:
473
+ if self.is_null():
474
+ return DataFrame()
475
+ return DataFrame(self.to_dict())
476
+
477
+ def to_excel(self, file:File):
478
+ if file.extension() != '.xlsx':
479
+ raise ValueError(f'{__class__.__name__} o arquivo precisa de do tipo .xlsx')
480
+ self.to_data().to_excel(file.absolute(), index=False)