dprojectstools 0.0.7__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,4 @@
1
+ # xmenu/__init__.py
2
+ #from .commands import command, example, CommandsManager
3
+ #from .secrets import SecretsManager
4
+ #from .crypto import aes_encrypt, aes_decrypt
@@ -0,0 +1,3 @@
1
+ # xmenu/__init__.py
2
+ from .restic import Restic
3
+
@@ -0,0 +1,62 @@
1
+ import os
2
+ import subprocess
3
+ from typing import Annotated
4
+ from ..commands import command, CommandsManager
5
+
6
+ class Restic:
7
+
8
+ # vars
9
+ _repository: str
10
+ _repository_password: str
11
+ _aws_access_key_id: str
12
+ _aws_secret_access_key: str
13
+
14
+ # ctor
15
+ def __init__(self, repository, repository_password, aws_access_key_id, aws_secret_access_key):
16
+ self._repository = repository
17
+ self._repository_password = repository_password
18
+ self._aws_access_key_id = aws_access_key_id
19
+ self._aws_secret_access_key = aws_secret_access_key
20
+
21
+ # commands
22
+ @command("Init repository", index = 1)
23
+ def init(self):
24
+ return subprocess.run("restic init", env = self._getEnv())
25
+
26
+ @command("Backup ", index = 10)
27
+ def backup(self):
28
+ return subprocess.run("restic backup ./data ./data2 --verbose", env = self._getEnv())
29
+
30
+ @command("List snapshots ", index = 20)
31
+ def snapshots_list(self):
32
+ return subprocess.run("restic snapshots", env = self._getEnv())
33
+
34
+ @command("List snapshot contents ")
35
+ def snapshots_contents(self,
36
+ id: Annotated[str, "ID"]
37
+ ):
38
+ return subprocess.run("restic ls --long {0}".format(id), env = self._getEnv())
39
+
40
+ @command("Restore ")
41
+ def snapshots_restore(self,
42
+ id: Annotated[str, "ID"]
43
+ ):
44
+ return subprocess.run("restic restore {0} --target ./restore --verbose".format(id), env = self._getEnv())
45
+
46
+ # methods
47
+ def exec(self, argv):
48
+ commandsManager = CommandsManager()
49
+ commandsManager.register(self)
50
+ return commandsManager.execute(argv)
51
+
52
+ # utils
53
+ def _getEnv(self):
54
+ myenv = os.environ.copy()
55
+ myenv['RESTIC_REPOSITORY'] = self._repository
56
+ myenv['RESTIC_PASSWORD'] = self._repository_password
57
+ myenv["AWS_ACCESS_KEY_ID"] = self._aws_access_key_id
58
+ myenv['AWS_SECRET_ACCESS_KEY'] = self._aws_secret_access_key
59
+ return myenv
60
+
61
+
62
+
@@ -0,0 +1,3 @@
1
+ # xmenu/__init__.py
2
+ from .commands import command, example, CommandsManager
3
+
@@ -0,0 +1,311 @@
1
+ import inspect
2
+ import types
3
+ from typing import List, get_type_hints
4
+ from dataclasses import dataclass
5
+
6
+ # global vars
7
+ order = 0
8
+ SEQUENCE_COLOR_RED = "\033[31m"
9
+ SEQUENCE_COLOR_GREEN = "\033[32m"
10
+ SEQUENCE_COLOR_GREEN_DARK = "\033[2;32m"
11
+ SEQUENCE_COLOR_GRAY_LIGHT = "\033[38;5;250m"
12
+ SEQUENCE_COLOR_GRAY_MEDIUM = "\033[38;5;244m"
13
+ SEQUENCE_COLOR_GRAY_DARK = "\033[38;5;238m"
14
+ SEQUENCE_RESET = "\033[0m"
15
+
16
+
17
+ # decorator (las funciones con este decorator, se catalogaran en el CommandsManager)
18
+ def command(title: str, index: int = 0):
19
+ def decorator(func):
20
+ global order
21
+ order += 1
22
+ aux = func.__name__ .split("_")
23
+ setattr(func, "title", title)
24
+ setattr(func, "order", order)
25
+ setattr(func, "index", index)
26
+ return func
27
+ return decorator
28
+
29
+
30
+ # decorator
31
+ def example(example: str):
32
+ def decorator(func):
33
+ setattr(func, "example", example)
34
+ return func
35
+ return decorator
36
+
37
+
38
+ # data classes
39
+ @dataclass
40
+ class CommandArgument:
41
+ name: str
42
+ title: str
43
+ required: bool
44
+ type: str
45
+ default: any
46
+
47
+ # data classes
48
+ @dataclass
49
+ class Command:
50
+ name: List[str]
51
+ title: str
52
+ arguments: List[CommandArgument]
53
+ instance: object
54
+ func: any
55
+ order:int
56
+ index: int
57
+
58
+
59
+ # Manager
60
+ class CommandsManager:
61
+
62
+ def __init__(self, title="", indent = 0):
63
+ # ctor
64
+ self._title = title
65
+ self._indent = indent
66
+ self._name = ""
67
+ self._argv = []
68
+ self._commands = []
69
+ self.exitCode = 0
70
+
71
+ # methods
72
+ def register(self, instance=None, prefix = ""):
73
+ if instance is None:
74
+ for name, obj in inspect.getmembers(__import__('__main__')):
75
+ if inspect.isfunction(obj) and name.startswith(prefix) and hasattr(obj, "title"):
76
+ self.registerFunction(None, obj, prefix)
77
+ else:
78
+ for name, obj in inspect.getmembers(type(instance), predicate=inspect.isfunction):
79
+ if inspect.isfunction(obj) and name.startswith(prefix) and hasattr(obj, "title"):
80
+ self.registerFunction(instance, obj, prefix)
81
+
82
+ def registerFunction(self, instance, func, prefix = ""):
83
+ # carga los Commands partiendo de las funciones cargadas en memoria, que empiezan con un determinado prefijo
84
+ func_name = func.__name__
85
+ command_name = func_name[len(prefix):].split("_")
86
+ command_title = getattr(func, "title")
87
+ command_order = getattr(func, "order")
88
+ command_index = getattr(func, "index")
89
+ command_arguments = []
90
+ for param_name, param in inspect.signature(func).parameters.items():
91
+ if param_name == "self":
92
+ continue
93
+ # argument name
94
+ command_argument_name = param_name
95
+ # argument title
96
+ command_argument_title = ""
97
+ if hasattr(param.annotation, "__metadata__"):
98
+ command_argument_title = param.annotation.__metadata__[0]
99
+ # argument title
100
+ command_argument_required = (param.default == inspect.Parameter.empty)
101
+ # argument type
102
+ command_argument_type = ""
103
+ if hasattr(param.annotation, "__metadata__"):
104
+ command_argument_type = param.annotation.__origin__.__name__
105
+ # argument default
106
+ command_argument_default = None
107
+ if param.default != inspect.Parameter.empty:
108
+ command_argument_default = param.default
109
+ # crea CommandArgument
110
+ command_argument = CommandArgument(command_argument_name, command_argument_title, command_argument_required, command_argument_type, command_argument_default)
111
+ command_arguments.append(command_argument)
112
+ # crea Comand
113
+ command = Command(name=command_name, title=command_title, arguments=command_arguments, instance=instance, func=func, order=command_order, index=command_index)
114
+ # añade a la lista de Commands
115
+ self._commands.append(command)
116
+
117
+ def sort(self):
118
+ # ordena los commands por order de declaracion de la funcion
119
+ self._commands.sort(key=lambda x: x.order)
120
+
121
+ # asigna indices
122
+ command_ant = None
123
+ index = 0
124
+ for command in self._commands:
125
+ if command.index != 0:
126
+ index = command.index
127
+ elif command_ant != None and command_ant.name[0] != command.name[0]:
128
+ index = int((index + 10) / 10) * 10
129
+ else:
130
+ index += 1
131
+ command.index = index
132
+ command_ant = command
133
+
134
+ self._commands.sort(key=lambda x: x.index)
135
+
136
+ def showMenu(self):
137
+ # sort
138
+ self.sort()
139
+ # title
140
+ if not self._title == "":
141
+ print(self._title)
142
+ print("*********")
143
+ # menu
144
+ indent = self._indent * " "
145
+ while True:
146
+ # show menu
147
+ print(indent + "Select an option:")
148
+ print(indent + "=================")
149
+ command_ant = None
150
+ for command in self._commands:
151
+ if command_ant == None and command.index > 1:
152
+ print(indent + f" : ")
153
+ if command_ant != None and command_ant.name[0] != command.name[0]:
154
+ print(indent + f" : ")
155
+ print(indent + f"{command.index:2} : {command.title}")
156
+ command_ant = command
157
+ print(indent + f" : ")
158
+ # read option
159
+ command_to_execute = None
160
+ while command_to_execute == None:
161
+ opcion = input(f" ? : ")
162
+ if opcion == "":
163
+ return 0
164
+ try:
165
+ opcion_index =int(opcion)
166
+ for command in self._commands:
167
+ if command.index == opcion_index:
168
+ command_to_execute = command
169
+ break
170
+ except:
171
+ pass
172
+ if command_to_execute == None:
173
+ print(indent + f"{SEQUENCE_COLOR_RED} índice no válido: {opcion}{SEQUENCE_RESET}")
174
+ # prepara argumentos
175
+ print()
176
+ exec_args = {}
177
+ errors = False
178
+ if len(command_to_execute.arguments) > 0:
179
+ for argument in command_to_execute.arguments:
180
+ argument_attributes = []
181
+ if argument.default != None:
182
+ argument_attributes.append(f"default es '{argument.default}'")
183
+ if argument.required:
184
+ argument_attributes.append(f"*")
185
+ argument_value = input(f" {argument.title}{"" if len(argument_attributes) == 0 else f" ({" ".join(argument_attributes)})"}: ").strip()
186
+ # valor por defecto
187
+ if not argument_value and argument.default != None:
188
+ argument_value = argument.default
189
+ # valida si es requerido
190
+ if argument.required:
191
+ if argument_value == "":
192
+ print(indent + f"{SEQUENCE_COLOR_RED} Error: argumento requerido: '{argument.title}'{SEQUENCE_RESET}")
193
+ errors = True
194
+ break
195
+ # convierte el tipo
196
+ try:
197
+ if argument.type == "int":
198
+ argument_value = int(argument_value)
199
+ elif argument.type == "float":
200
+ argument_value = float(argument_value)
201
+ elif argument.type == "List":
202
+ argument_value = argument_value.split(",")
203
+ except:
204
+ print(indent + f"{SEQUENCE_COLOR_RED} Error: el argumento '{argument.name}' no se ha podido convertir al tipo '{argument.type}': {argument_value}{SEQUENCE_RESET}")
205
+ errors = True
206
+ break
207
+ # set
208
+ exec_args[argument.name] = argument_value;
209
+ # exec
210
+ if not errors:
211
+ # invoke
212
+ if not command_to_execute.instance is None:
213
+ func_bounded = types.MethodType(command_to_execute.func, command_to_execute.instance)
214
+ result = func_bounded(**exec_args)
215
+ else:
216
+ result = command_to_execute.func(**exec_args)
217
+ # empty line
218
+ print()
219
+
220
+ def showHelp(self):
221
+ print(f"usage: {self._name} [<command> [<args>]]")
222
+ print("")
223
+ print("Commands:")
224
+ for command in self._commands:
225
+ line = " "
226
+ line += "Adasd"
227
+ args = []
228
+ for argument in command.arguments:
229
+ args.append(f"--{argument.name}")
230
+ args.append(f"{SEQUENCE_COLOR_GRAY_MEDIUM}<{argument.title}>{SEQUENCE_RESET}")
231
+ print(f" {" ".join(command.name):10} {" ".join(args)} {SEQUENCE_COLOR_GREEN_DARK}# {command.title}{SEQUENCE_RESET}")
232
+
233
+ def execute(self, argv):
234
+ # init
235
+ self._name = argv[0]
236
+ self._argv = argv
237
+ # ejecuta el comando que toque, segun self._argv
238
+ if len(self._argv)==1:
239
+ self.showMenu()
240
+ return
241
+ if len(self._argv)==2 and (self._argv[1] == "--help" or self._argv[1] == "-h"):
242
+ self.showHelp()
243
+ return
244
+ # busca el commando a ejecutar
245
+ command_to_execute = None
246
+ for command in self._commands:
247
+ if len(command.name) <= len(argv) - 1:
248
+ if command.name == argv[1:len(command.name)+1]:
249
+ command_to_execute = command
250
+ break
251
+ if command_to_execute:
252
+ break
253
+ # si no se ha encontrado, muestra el mesnaje de error
254
+ if command_to_execute == None:
255
+ print(f"error: no se ha encontrado el comando")
256
+ return -1
257
+ # ejecuta el comando
258
+ command_args = argv[len(command_to_execute.name)+1:]
259
+ command_args_dict = {}
260
+ command_args_errors = False
261
+ for i in range(0, len(command_args), 2):
262
+ if command_args[i].startswith('--'):
263
+ key = command_args[i][2:]
264
+ value = command_args[i + 1] if i + 1 < len(command_args) else None
265
+ command_args_dict[key] = value
266
+ # valida que no sobre ningun argumento
267
+ for key in command_args_dict.keys():
268
+ if not key in [argument.name for argument in command_to_execute.arguments]:
269
+ print(f"error: argumento inválido: --{key}")
270
+ command_args_errors = True
271
+ # aañade defaults
272
+ for argument in command.arguments:
273
+ if not argument.name in command_args_dict:
274
+ if argument.default != None:
275
+ command_args_dict[argument.name] = argument.default
276
+ # valida que no falte ningun argumento
277
+ for argument in command.arguments:
278
+ if not argument.name in command_args_dict:
279
+ print(f"error: argumento obligatorio: --{argument.name}")
280
+ command_args_errors = True
281
+ # valida que el tipo de argumentos sea correcto
282
+ if not command_args_errors:
283
+ for argument in command.arguments:
284
+ argument_value = command_args_dict[argument.name]
285
+ try:
286
+ if argument.type == "int":
287
+ argument_value = int(argument_value)
288
+ elif argument.type == "float":
289
+ argument_value = float(argument_value)
290
+ elif argument.type == "List":
291
+ argument_value = argument_value.split(",")
292
+ command_args_dict[argument.name] = argument_value
293
+ except:
294
+ print(f"{SEQUENCE_COLOR_RED}error: el argumento '{argument.name}' no se ha podido convertir al tipo '{argument.type}': {argument_value}{SEQUENCE_RESET}")
295
+ command_args_errors = True
296
+ break
297
+ # si hay errores
298
+ if command_args_errors:
299
+ return -1
300
+ # invoke
301
+ if not command_to_execute.instance is None:
302
+ func_bounded = types.MethodType(command_to_execute.func, command_to_execute.instance)
303
+ return func_bounded(**command_args_dict)
304
+ else:
305
+ return command_to_execute.func(**command_args_dict)
306
+
307
+
308
+
309
+
310
+
311
+
@@ -0,0 +1 @@
1
+ from .aes import aes_encrypt, aes_decrypt, password_generate
@@ -0,0 +1,130 @@
1
+ from pathlib import Path
2
+ from cryptography.fernet import Fernet # pip install cryptography
3
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
4
+ from cryptography.hazmat.primitives.hashes import SHA256
5
+ from cryptography.hazmat.primitives import hashes
6
+ from cryptography.hazmat.primitives import padding
7
+ from cryptography.hazmat.backends import default_backend
8
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
9
+ import base64
10
+ import io
11
+ import os
12
+ import struct
13
+ import random
14
+ import hashlib
15
+ import random
16
+ import string
17
+
18
+ # utils
19
+ def password_generate(min=10, max=32):
20
+ length = random.randint(min, max)
21
+ characters = string.ascii_letters + string.digits + string.punctuation
22
+ password = ''.join(random.choice(characters) for i in range(length))
23
+ return password
24
+
25
+ # utils
26
+ def aes_encrypt(text: str, password: str):
27
+ iterations = 0
28
+ saltLength = 16
29
+ ivLength = 16
30
+ blockSize = 16
31
+ paddingMode = "PKCS7"
32
+ cipherMode = "CBC"
33
+ keySize = 32
34
+ encoding = "base64"
35
+ separator = ','
36
+ version = ""
37
+ fold = 76
38
+ # salt
39
+ salt = os.urandom(saltLength)
40
+ # iterations
41
+ iterations = int(random.uniform(50000, 50000))
42
+ # derive key
43
+ key = hashlib.pbkdf2_hmac(
44
+ hash_name='sha256', # HMAC-SHA256
45
+ password=password.encode(), # Password as bytes
46
+ salt=salt, # Salt as bytes
47
+ iterations=iterations, # Iteration count
48
+ dklen=keySize # Length of the derived key
49
+ )
50
+ # iv
51
+ iv = os.urandom(ivLength)
52
+ # ms
53
+ body_ms = io.BytesIO();
54
+ # write iv
55
+ body_ms.write(iv)
56
+ # write salt
57
+ body_ms.write(salt)
58
+ # write iterations
59
+ body_ms.write(bytearray(iterations.to_bytes(4, byteorder='little')))
60
+ # cipher
61
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
62
+ # encript
63
+ encryptor = cipher.encryptor()
64
+ # Pad the plaintext to a multiple of 16 bytes
65
+ padder = padding.PKCS7(blockSize * 8).padder()
66
+ padded_plaintext = padder.update(base64.b64encode(salt) + text.encode()) + padder.finalize()
67
+ #print(padded_plaintext)
68
+ # crypt
69
+ ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
70
+ body_ms.write(ciphertext)
71
+ # read
72
+ body_ms.seek(0)
73
+ body = body_ms.read()
74
+ body_ms.close()
75
+ # result
76
+ result = "aes:" + separator + base64.b64encode(body).decode()
77
+ # fold
78
+ result = "\n".join(
79
+ result[i:i + fold] for i in range(0, len(result), fold)
80
+ )
81
+ # return
82
+ return result
83
+
84
+ def aes_decrypt(text: str, password: str):
85
+ iterations = 0
86
+ saltLength = 16
87
+ ivLength = 16
88
+ blockSize = 16
89
+ paddingMode = "PKCS7"
90
+ cipherMode = "CBC"
91
+ keySize = 32
92
+ encoding = "base64"
93
+ separator = ','
94
+ version = ""
95
+ # header
96
+ header = text[:text.index(",")]
97
+ # body
98
+ body = text[text.index(",")+1:]
99
+ body_bytes = base64.b64decode(body)
100
+ stream = io.BytesIO(body_bytes)
101
+ # read iv
102
+ iv = stream.read(ivLength)
103
+ # read salt
104
+ salt = stream.read(saltLength)
105
+ # read iterations
106
+ iterations = struct.unpack('<I', stream.read(4))[0]
107
+ # derive key
108
+ key = hashlib.pbkdf2_hmac(
109
+ hash_name='sha256', # HMAC-SHA256
110
+ password=password.encode(), # Password as bytes
111
+ salt=salt, # Salt as bytes
112
+ iterations=iterations, # Iteration count
113
+ dklen=keySize # Length of the derived key
114
+ )
115
+ # cipher
116
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
117
+ # decryptor
118
+ decryptor = cipher.decryptor()
119
+ # decrypt
120
+ decrypted_result = decryptor.update(stream.read()) + decryptor.finalize()
121
+ # remove padding
122
+ unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
123
+ decrypted_result = unpadder.update(decrypted_result) + unpadder.finalize()
124
+ # decode
125
+ result = decrypted_result.decode()
126
+ # skip salt
127
+ result = result[int(saltLength*1.5):]
128
+ # return
129
+ return result
130
+
@@ -0,0 +1 @@
1
+ from .secrets import SecretsManager
@@ -0,0 +1,109 @@
1
+ from pathlib import Path
2
+ import os
3
+ import json
4
+ import keyring
5
+ from typing import Annotated
6
+ from ..commands import command, CommandsManager
7
+ from ..crypto import aes_decrypt, aes_encrypt, password_generate
8
+
9
+ # consts
10
+ KEYRING_APP = "dprojectstools"
11
+
12
+ # class
13
+ class SecretsManager():
14
+
15
+
16
+ # vars
17
+ _password = None
18
+ _path = ""
19
+ _dict = {}
20
+
21
+
22
+ # ctr
23
+ def __init__(self, name, password = "keyring:"):
24
+ folder = Path(os.path.join(Path.home(), ".secrets"))
25
+ folder.mkdir(parents=True, exist_ok=True)
26
+ self._path = Path(folder, name + ".json")
27
+ if not password == "":
28
+ if password.startswith("keyring:"):
29
+ username = password[password.index(":") + 1:]
30
+ if username == "":
31
+ username = os.getlogin()
32
+ password = keyring.get_password(KEYRING_APP, username)
33
+ if password is None:
34
+ password = password_generate()
35
+ keyring.set_password(KEYRING_APP, username, password)
36
+ self._password = password
37
+ self._path = Path(folder, name + ".json.aes")
38
+ self._load()
39
+
40
+
41
+ # methods
42
+ def get(self, name):
43
+ value = self._dict.get(name)
44
+ if value is None:
45
+ value = self.set(name, value)
46
+ return str(value)
47
+
48
+ def keys(self):
49
+ return self._dict.keys()
50
+
51
+ def set(self, name, value=None):
52
+ if value is None:
53
+ value = input("Enter secret '{0}' value: ".format(name))
54
+ self._dict[name] = str(value)
55
+ self._save()
56
+ return value
57
+
58
+ def delete(self, name):
59
+ del self._dict[name]
60
+ self._save()
61
+
62
+
63
+ # methods
64
+ def _load(self):
65
+ if os.path.isfile(self._path):
66
+ with open(self._path, "r") as file:
67
+ text = file.read()
68
+ if not self._password is None:
69
+ text = aes_decrypt(text, self._password);
70
+ self._dict = json.loads(text)
71
+ pass
72
+
73
+ def _save(self):
74
+ text = json.dumps(self._dict, indent=4)
75
+ if not self._password is None:
76
+ text = aes_encrypt(text, self._password);
77
+ with open(self._path, "w") as file:
78
+ file.write(text)
79
+
80
+
81
+ # commands
82
+ @command("List secrets", index = 90)
83
+ def secrets_list(self):
84
+ for key in self.keys():
85
+ print("{0}: {1}".format(key, self.get(key)))
86
+ @command("Set secret")
87
+ def secrets_set(self,
88
+ name: Annotated[str, "Name"],
89
+ value: Annotated[str, "Value"]
90
+ ):
91
+ self.set(name, value)
92
+ @command("Get secret")
93
+ def secrets_get(self,
94
+ name: Annotated[str, "Name"]
95
+ ):
96
+ value = self.get(name)
97
+ print(value)
98
+ @command("Del secret")
99
+ def secrets_delete(self,
100
+ name: Annotated[str, "Name"]
101
+ ):
102
+ self.delete(name)
103
+
104
+ # methods
105
+ def exec(self, argv):
106
+ commandsManager = CommandsManager()
107
+ commandsManager.register(self)
108
+ return commandsManager.execute(argv)
109
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Marc Delòs Poch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.2
2
+ Name: dprojectstools
3
+ Version: 0.0.7
4
+ Summary: A set of development tools
5
+ Author-email: Marc Delos <marcdp@dprojects.com>
6
+ Project-URL: Homepage, https://github.com/marcdp/dprojectstools
7
+ Project-URL: Issues, https://github.com/marcdp/dprojectstools/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # dprojectstools
16
+ A set of development tools
@@ -0,0 +1,14 @@
1
+ dprojectstools/__init__.py,sha256=_RjYtKMfjjsymn9dV5noeotczZkhk928Fxo9h675EOE,164
2
+ dprojectstools/backups/__init__.py,sha256=NV15vstbHrGns8LgKWYA3ssnjjjlluU-s29gI7APmlg,51
3
+ dprojectstools/backups/restic.py,sha256=Hjyzt6FXf9FtJb2YdFzahn9JeHOyD93GlqiE420nO9o,2023
4
+ dprojectstools/commands/__init__.py,sha256=VZmFrfX6F6NleK3z4pIA6GmuM9pygQxUOEXleVqJB3E,80
5
+ dprojectstools/commands/commands.py,sha256=UDU41t6oNRxFTrK9I8YqWI5UZD3_w1WKcDDjvaZWE18,12882
6
+ dprojectstools/crypto/__init__.py,sha256=CAPzDNcUXRa9zBM5CKzcMjtkuOCQojhaKp4GJ0JtcqE,62
7
+ dprojectstools/crypto/aes.py,sha256=qTJ-eHzRUB8UalnqJPkG62k3oxuy76QiEoZBFdSMpic,4186
8
+ dprojectstools/secrets/__init__.py,sha256=1-v3UV1A-FP_o1vcccii1T1wSXsj6nGWxWvxWzFmdBE,37
9
+ dprojectstools/secrets/secrets.py,sha256=Acl6nargoLesu1ZQ2zG6-ycLomTNowzyees2LQ5ILSs,3175
10
+ dprojectstools-0.0.7.dist-info/LICENSE,sha256=wog66J88bXIo-u2Axc3ia6UllVLpcW-TAsIoFX9DAr4,1094
11
+ dprojectstools-0.0.7.dist-info/METADATA,sha256=MXduNnTIp_PX0Y49566BO41S2hc7FvWRLG81VelYAEs,567
12
+ dprojectstools-0.0.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
13
+ dprojectstools-0.0.7.dist-info/top_level.txt,sha256=Rfed2TC6kuHWz0giVwl_Sc-ffhFooCeD4rlP84wr0hw,15
14
+ dprojectstools-0.0.7.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ dprojectstools