soup-files 1.2__tar.gz → 1.2.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soup_files
3
- Version: 1.2
3
+ Version: 1.2.1
4
4
  Summary: soup_files
5
5
  Author: Bruno Chaves
6
6
  Requires-Python: >=3.11
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "soup_files"
7
- version = "1.2"
7
+ version = "1.2.1"
8
8
  authors = [
9
9
  { name="Bruno Chaves"},
10
10
  ]
@@ -18,5 +18,5 @@ dependencies = [
18
18
 
19
19
  [tool.setuptools]
20
20
  packages = [
21
-
21
+ "soup_files"
22
22
  ]
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from .version import __version__
4
+ from .progress import ProgressBarAdapter, ProgressBarSimple, ABCProgressBar
5
+ from .files import (
6
+ File,
7
+ Directory,
8
+ JsonData,
9
+ JsonConvert,
10
+ InputFiles,
11
+ LibraryDocs,
12
+ UserFileSystem,
13
+ UserAppDir,
14
+ KERNEL_TYPE,
15
+ )
16
+
@@ -0,0 +1,486 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ """
4
+ Esté módulo serve para manipulação de arquivos, diretórios e documentos
5
+ entre outros. Não depende de módulos externos apenas de builtins e stdlib.
6
+ """
7
+ from __future__ import annotations
8
+ from typing import List, Dict
9
+ from enum import Enum
10
+ import os
11
+ import json
12
+ import platform
13
+ from pathlib import Path
14
+ from hashlib import md5
15
+
16
+
17
+ # Windows / Linux / ...
18
+ KERNEL_TYPE = platform.system()
19
+
20
+
21
+ class LibraryDocs(Enum):
22
+
23
+ IMAGE = ['.png', '.jpg', '.jpeg', '.svg']
24
+ PDF = ['.pdf']
25
+ DOCUMENTS = ['.png', '.jpg', '.jpeg', '.svg', '.pdf']
26
+ #
27
+ EXCEL = ['.xlsx']
28
+ CSV = ['.csv', '.txt']
29
+ #
30
+ SHEET = ['.csv', '.txt', '.xlsx', '.xls']
31
+ JSON = ['.json']
32
+ #
33
+ ALL_DOCUMENTS = [
34
+ '.png', '.jpg', '.jpeg', '.svg',
35
+ '.csv', '.txt', '.xlsx',
36
+ '.pdf',
37
+ '.json',
38
+ ]
39
+ #
40
+ ALL = None
41
+
42
+
43
+ class File(object):
44
+ def __init__(self, filename: str):
45
+ if os.path.isdir(filename):
46
+ raise ValueError(f'{__class__.__name__} File() não pode ser um diretório.')
47
+ self.filename: str = os.path.abspath(filename)
48
+ self.__path: Path = Path(self.filename)
49
+
50
+ @property
51
+ def path(self) -> Path:
52
+ return self.__path
53
+
54
+ @path.setter
55
+ def path(self, new:Path):
56
+ if not isinstance(new, Path):
57
+ return
58
+ self.__path = new
59
+
60
+ def __eq__(self, value):
61
+ if not isinstance(value, File):
62
+ return NotImplemented
63
+ return self.absolute() == value.absolute()
64
+
65
+ def __hash__(self):
66
+ return self.absolute().__hash__()
67
+
68
+ def is_image(self) -> bool:
69
+ try:
70
+ return True if self.extension() in LibraryDocs.IMAGE.value else False
71
+ except:
72
+ return False
73
+
74
+ def is_pdf(self) -> bool:
75
+ try:
76
+ return True if self.extension() in LibraryDocs.PDF.value else False
77
+ except:
78
+ return False
79
+
80
+ def is_excel(self) -> bool:
81
+ try:
82
+ return True if self.extension() in LibraryDocs.EXCEL.value else False
83
+ except:
84
+ return False
85
+
86
+ def is_csv(self) -> bool:
87
+ try:
88
+ return True if self.extension() in LibraryDocs.CSV.value else False
89
+ except:
90
+ return False
91
+
92
+ def is_sheet(self) -> bool:
93
+ try:
94
+ return True if self.extension() in LibraryDocs.SHEET.value else False
95
+ except:
96
+ return False
97
+
98
+ def update_extension(self, e: str) -> File:
99
+ """
100
+ Retorna uma instância de File() no mesmo diretório com a nova
101
+ extensão informada.
102
+ """
103
+ current = self.extension()
104
+ full_path = self.absolute().replace(current, '')
105
+ return File(os.path.join(f'{full_path}{e}'))
106
+
107
+ def get_text(self) -> str | None:
108
+ try:
109
+ return self.__path.read_text()
110
+ except Exception as e:
111
+ print(e)
112
+ return None
113
+
114
+ def write_string(self, s:str):
115
+ self.__path.write_text(s)
116
+
117
+ def write_list(self, items: List[str]):
118
+ # Abrindo o arquivo em modo de escrita
119
+ with open(self.filename, "w", encoding="utf-8") as file:
120
+ for string in items:
121
+ file.write(string + "\n") # Adiciona uma quebra de linha após cada string
122
+
123
+ def name(self):
124
+ e = self.extension()
125
+ if (e is None) or (e == ''):
126
+ return os.path.basename(self.filename)
127
+ return os.path.basename(self.filename).replace(e, '')
128
+
129
+ def name_absolute(self) -> str:
130
+ e = self.extension()
131
+ if (e is None) or (e == ''):
132
+ return self.filename
133
+ return self.filename.replace(e, '')
134
+
135
+ def extension(self) -> str:
136
+ return self.__path.suffix
137
+
138
+ def dirname(self) -> str:
139
+ return os.path.dirname(self.filename)
140
+
141
+ def basename(self) -> str:
142
+ return os.path.basename(self.filename)
143
+
144
+ def exists(self) -> bool:
145
+ return self.__path.exists()
146
+
147
+ def absolute(self) -> str:
148
+ return self.filename
149
+
150
+ def size(self):
151
+ return os.path.getsize(self.filename)
152
+
153
+ def md5(self) -> str | None:
154
+ """Retorna a hash md5 de um arquivo se ele existir no disco."""
155
+ if not self.path.exists():
156
+ return None
157
+ _hash_md5 = md5()
158
+ with open(self.absolute(), "rb") as f:
159
+ for _block in iter(lambda: f.read(4096), b""):
160
+ _hash_md5.update(_block)
161
+ return _hash_md5.hexdigest()
162
+
163
+
164
+ class Directory(object):
165
+ def __init__(self, dirpath:str):
166
+ self.dirpath: str = os.path.abspath(dirpath)
167
+ self.path: Path = Path(self.dirpath)
168
+
169
+ def __eq__(self, value):
170
+ if not isinstance(value, Directory):
171
+ return NotImplemented
172
+ return self.absolute() == value.absolute()
173
+
174
+ def __hash__(self):
175
+ return self.absolute().__hash__()
176
+
177
+ def iterpaths(self) -> List[Path]:
178
+ return self.path.rglob('*')
179
+
180
+ def __content_recursive(self) -> List[File]:
181
+ _paths = self.iterpaths()
182
+ values = []
183
+ for p in _paths:
184
+ if p.is_file():
185
+ values.append(
186
+ File(
187
+ os.path.abspath(p.absolute())
188
+ )
189
+ )
190
+ return values
191
+
192
+ def __content_no_recursive(self) -> List[File]:
193
+ files = os.listdir(self.absolute())
194
+ values: List[File] = []
195
+ for f in files:
196
+ if os.path.isfile(f):
197
+ values.append(
198
+ File(os.path.abspath(f))
199
+ )
200
+ return values
201
+
202
+ def content_files(self, *, recursive: bool = True) -> List[File]:
203
+ if recursive:
204
+ return self.__content_recursive()
205
+ return self.__content_no_recursive()
206
+
207
+ def content_dirs(self, recursive: bool = True) -> List[Directory]:
208
+ values: List[Directory] = []
209
+ if recursive:
210
+ _paths = self.iterpaths()
211
+ for p in _paths:
212
+ if p.is_dir():
213
+ values.append(
214
+ Directory(os.path.abspath(p.absolute()))
215
+ )
216
+ else:
217
+ _paths = os.listdir(self.absolute())
218
+ for d in _paths:
219
+ if os.path.isdir(d):
220
+ values.append(
221
+ Directory(os.path.abspath(d))
222
+ )
223
+ return values
224
+
225
+ def basename(self) -> str:
226
+ return os.path.basename(self.absolute())
227
+
228
+ def mkdir(self):
229
+ try:
230
+ os.makedirs(self.absolute())
231
+ except:
232
+ pass
233
+
234
+ def absolute(self) -> str:
235
+ return self.dirpath
236
+
237
+ def concat(self, d:str, create:bool=False) -> Directory:
238
+ if create == True:
239
+ try:
240
+ os.makedirs(os.path.join(self.absolute(), d))
241
+ except:
242
+ pass
243
+ return Directory(
244
+ os.path.join(self.absolute(), d)
245
+ )
246
+
247
+ def parent(self) -> Directory:
248
+ return Directory(
249
+ os.path.abspath(self.path.parent)
250
+ )
251
+
252
+ def join_file(self, name:str) -> File:
253
+ return File(
254
+ os.path.join(self.absolute(), name)
255
+ )
256
+
257
+
258
+ class InputFiles(object):
259
+ """
260
+ Obeter uma lista de arquivos/documentos do diretório informado.
261
+ """
262
+ def __init__(self, d: Directory, *, maxFiles: int = 5000):
263
+ if not isinstance(d, Directory):
264
+ raise ValueError(f'{__class__.__name__}\nUse: Directory(), não {type(d)}')
265
+ self.input_dir: Directory = d
266
+ self.maxFiles: int = maxFiles
267
+
268
+ def get_files_with(self, *, infile: str, sort: bool = True) -> List[File]:
269
+ """
270
+ Retorna arquivos que contém a ocorrência (infile) no nome absoluto.
271
+ """
272
+ content_files: List[File] = []
273
+ count: int = 0
274
+ paths = self.input_dir.iterpaths()
275
+ for file in paths:
276
+ if not file.is_file():
277
+ continue
278
+ if infile in os.path.abspath(file.absolute()):
279
+ content_files.append(
280
+ File(os.path.abspath(file.absolute()))
281
+ )
282
+ count += 1
283
+ if count >= self.maxFiles:
284
+ break
285
+ return content_files
286
+
287
+ def __get_files_recursive(self, *, file_type: LibraryDocs, sort: bool) -> List[File]:
288
+ #
289
+ #
290
+ _paths: List[Path] = self.input_dir.iterpaths()
291
+ _all_files = []
292
+ count: int = 0
293
+ if file_type == LibraryDocs.ALL:
294
+ # Todos os tipos de arquivos
295
+ for p in _paths:
296
+ if not p.is_file():
297
+ continue
298
+ _all_files.append(
299
+ File(os.path.abspath(p.absolute()))
300
+ )
301
+ count += 1
302
+ if count >= self.maxFiles:
303
+ break
304
+ else:
305
+ # Arquivos especificados em LibraryDocs
306
+ for p in _paths:
307
+ if not p.is_file():
308
+ continue
309
+ if (p.suffix is None) or (p.suffix == ''):
310
+ continue
311
+ if p.suffix in file_type.value:
312
+ _all_files.append(
313
+ File(os.path.abspath(p.absolute()))
314
+ )
315
+ count += 1
316
+ if count >= self.maxFiles:
317
+ break
318
+ if sort:
319
+ _all_files.sort(key=File.absolute)
320
+ return _all_files
321
+
322
+ def __get_files_no_recursive(self, *, file_type: LibraryDocs, sort: bool):
323
+ _paths: List[str] = os.listdir(self.input_dir.absolute())
324
+ _all_files = []
325
+ count: int = 0
326
+ if file_type == LibraryDocs.ALL:
327
+ # Todos os tipos de arquivos
328
+ for p in _paths:
329
+ if os.path.isfile(p):
330
+ _all_files.append(
331
+ File(os.path.abspath(p))
332
+ )
333
+ count += 1
334
+ if count >= self.maxFiles:
335
+ break
336
+ else:
337
+ # Arquivos especificados em LibraryDocs
338
+ for p in _paths:
339
+ if os.path.isfile(p):
340
+ _path_file = Path(p)
341
+ if (_path_file.suffix is None) or (_path_file.suffix == ''):
342
+ continue
343
+ if _path_file.suffix in file_type.value:
344
+ _all_files.append(
345
+ File(os.path.abspath(_path_file.absolute()))
346
+ )
347
+ count += 1
348
+ if count >= self.maxFiles:
349
+ break
350
+ if sort:
351
+ _all_files.sort(key=File.absolute)
352
+ return _all_files
353
+
354
+ def get_files(
355
+ self, *,
356
+ file_type: LibraryDocs = LibraryDocs.ALL_DOCUMENTS,
357
+ sort: bool = True,
358
+ recursive: bool = True
359
+ ) -> List[File]:
360
+ """
361
+ Retorna uma lista de File() de acordo com o tipo de arquivo
362
+ especificado.
363
+ - LibraryDocs.ALL_DOCUMENTS => Retorna todos os documentos do diretório.
364
+ - LibraryDocs.EXCEL => Retorna arquivos que são planilhas excel.
365
+ - ...
366
+
367
+ """
368
+ if recursive:
369
+ return self.__get_files_recursive(file_type=file_type, sort=sort)
370
+ else:
371
+ return self.__get_files_no_recursive(file_type=file_type, sort=sort)
372
+
373
+
374
+ class JsonData(object):
375
+ """
376
+ Representação de um dado JSON apartir de uma string python.
377
+ """
378
+ def __init__(self, string:str):
379
+ if not isinstance(string, str):
380
+ raise ValueError(f'{__class__.__name__} o JSON informado precisa ser do tipo string, não {type(string)}')
381
+ self.jsonString:str = string
382
+
383
+ def is_null(self) -> bool:
384
+ if (self.jsonString is None) or (self.jsonString == ''):
385
+ return True
386
+ return False
387
+
388
+ def to_string(self) -> str:
389
+ return self.jsonString
390
+
391
+ def to_dict(self) -> Dict[str, object]:
392
+ """
393
+ Exportar/Converter o dado atual em um dicionário python.
394
+ """
395
+ return json.loads(self.jsonString)
396
+
397
+ def to_file(self, f:File):
398
+ """Exportar o dado atual para um arquivo .json"""
399
+ _data:str = json.loads(self.jsonString)
400
+ with open(f.absolute(), "w", encoding="utf-8") as file:
401
+ json.dump(_data, file, indent=4, ensure_ascii=False)
402
+
403
+
404
+ class JsonConvert(object):
405
+ """
406
+ Conversão de um dado JSON em dados python
407
+ """
408
+ def __init__(self, jsonData: JsonData):
409
+ self.jsonData: JsonData = jsonData
410
+
411
+ def to_json_data(self) -> JsonData:
412
+ return self.jsonData
413
+
414
+ @classmethod
415
+ def from_file(cls, file:File) -> JsonConvert:
416
+ """
417
+ Gerar um dado JsonData apartir de um arquivo .json
418
+ """
419
+ # Ler o arquivo e carregar o JSON em um dicionário Python
420
+ data = None
421
+ try:
422
+ with open(file.absolute(), "r", encoding="utf-8") as fp:
423
+ data:str = json.load(fp)
424
+ except Exception as e:
425
+ print(f'{__class__.__name__}\n{e}')
426
+ return cls(JsonData(''))
427
+ else:
428
+ #return JsonData(json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True))
429
+ return cls(JsonData(json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)))
430
+
431
+ @classmethod
432
+ def from_string_json(cls, data:str) -> JsonConvert:
433
+ """
434
+ Gerar um dado JsonData apartir de uma string.
435
+ """
436
+ json_string = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
437
+ return cls(JsonData(json_string))
438
+
439
+ @classmethod
440
+ def from_dict(cls, data:Dict[str, object]) -> JsonConvert:
441
+ """
442
+ Converte um dicionário em objeto JSON/JsonData.
443
+ """
444
+ if not isinstance(data, dict):
445
+ raise ValueError(f'{__class__.__name__} Informe um JSON em formato dict, não {type(data)}')
446
+ json_string = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
447
+ return cls(JsonData(json_string))
448
+
449
+
450
+ class UserFileSystem(object):
451
+ """
452
+ Diretórios comuns para cache e configurações de usuário.
453
+ """
454
+ def __init__(self, base_home:Directory=None):
455
+ if base_home is None:
456
+ self.baseHome:Directory = Directory(os.path.abspath(Path().home()))
457
+ elif isinstance(base_home, Directory):
458
+ self.baseHome:Directory = base_home
459
+ else:
460
+ raise ValueError(f'{__class__.__name__}\nUse:Directory(), não {type(base_home)}')
461
+ self.userDownloads:Directory = self.baseHome.concat('Downloads', create=True)
462
+ self.userVarDir:Directory = self.baseHome.concat('var', create=True)
463
+
464
+ def config_dir(self) -> Directory:
465
+ return self.userVarDir.concat('config', create=True)
466
+
467
+ def cache_dir(self) -> Directory:
468
+ return self.userVarDir.concat('cache', create=True)
469
+
470
+
471
+ class UserAppDir(object):
472
+ """
473
+ Diretório comun para cache e configurações do aplicativo.
474
+ """
475
+ def __init__(self, appname:str, *, user_file_system:UserFileSystem=UserFileSystem()):
476
+ self.appname = appname
477
+ self.userFileSystem:UserFileSystem = user_file_system
478
+ self.workspaceDirApp:Directory = self.userFileSystem.userDownloads.concat(self.appname, create=True)
479
+ self.installDir:Directory = self.userFileSystem.userVarDir.concat('opt').concat(self.appname, create=True)
480
+
481
+ def cache_dir_app(self) -> Directory:
482
+ return self.userFileSystem.cache_dir().concat(self.appname, create=True)
483
+
484
+ def config_dir_app(self) -> Directory:
485
+ return self.userFileSystem.config_dir().concat(self.appname, create=True)
486
+
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class ABCProgressBar(ABC):
7
+ """
8
+ Barra de progresso Abstrata
9
+ """
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self._num_progress: float = 0
14
+ self.pbar_real: object = None
15
+
16
+ @property
17
+ def num_progress(self) -> float:
18
+ return self._num_progress
19
+
20
+ @num_progress.setter
21
+ def num_progress(self, new: float):
22
+ if isinstance(new, float):
23
+ self._num_progress = new
24
+ return
25
+ try:
26
+ _prog = float(new)
27
+ except Exception as e:
28
+ print(e)
29
+ else:
30
+ self._num_progress = _prog
31
+
32
+ @abstractmethod
33
+ def set_percent(self, percent: float):
34
+ """Seta o progresso com float de porcentagem, ex: '42.8'"""
35
+ pass
36
+
37
+ @abstractmethod
38
+ def set_text(self, text: str):
39
+ """Seta um texto indicando a situação atual"""
40
+ pass
41
+
42
+ def start(self):
43
+ """Inicia a barra de progresso (pode ser vazio dependendo da implementação)"""
44
+ pass
45
+
46
+ def stop(self):
47
+ """Para a barra de progresso (pode ser vazio dependendo da implementação)"""
48
+ pass
49
+
50
+
51
+ class ProgressBarSimple(ABCProgressBar):
52
+ """Barra de progresso simples para mostrar no terminal."""
53
+
54
+ def __init__(self, simple_pbar=None):
55
+ super().__init__()
56
+ self.pbar_real = simple_pbar
57
+ self._text: str = 'Aguarde!'
58
+ self.num_progress: float = 0
59
+
60
+ def set_percent(self, percent: float):
61
+ if not isinstance(percent, float):
62
+ return
63
+ if len(f'{percent}') > 4:
64
+ percent = round(float(percent), 2)
65
+ self.num_progress = percent
66
+ #print(f'[{self.num_progress}%] {self._text}', end='\r')
67
+
68
+ def set_text(self, text: str):
69
+ self._text = text
70
+ print(f'[{self.num_progress}%] {self._text}', end='\r')
71
+
72
+ def start(self):
73
+ pass
74
+
75
+ def stop(self):
76
+ pass
77
+
78
+
79
+ class ProgressBarAdapter(object):
80
+ def __init__(self, progress_bar: ABCProgressBar = ProgressBarSimple()):
81
+ self.pbar_implement: ABCProgressBar = progress_bar
82
+
83
+ def get_current_percent(self) -> float:
84
+ return self.pbar_implement.num_progress
85
+
86
+ def update_text(self, text: str = "-"):
87
+ self.pbar_implement.set_text(text)
88
+
89
+ def update_percent(self, percent: float = 0):
90
+ if not isinstance(percent, float):
91
+ try:
92
+ percent = float(percent)
93
+ except Exception as e:
94
+ print(f'{__class__.__name__} {e}')
95
+ percent = 0
96
+ self.pbar_implement.set_percent(percent)
97
+
98
+ def update(self, percent: float, status: str = "-"):
99
+ self.update_percent(percent)
100
+ self.update_text(status)
101
+ #self.pbar_implement.set_text(status)
102
+
103
+ def start(self):
104
+ self.pbar_implement.start()
105
+
106
+ def stop(self):
107
+ self.pbar_implement.stop()
108
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env python3
2
+
3
+ __version__ = '1.2.1'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soup_files
3
- Version: 1.2
3
+ Version: 1.2.1
4
4
  Summary: soup_files
5
5
  Author: Bruno Chaves
6
6
  Requires-Python: >=3.11
@@ -1,5 +1,9 @@
1
1
  README.md
2
2
  pyproject.toml
3
+ soup_files/__init__.py
4
+ soup_files/files.py
5
+ soup_files/progress.py
6
+ soup_files/version.py
3
7
  soup_files.egg-info/PKG-INFO
4
8
  soup_files.egg-info/SOURCES.txt
5
9
  soup_files.egg-info/dependency_links.txt
@@ -0,0 +1 @@
1
+ soup_files
File without changes
File without changes