kbasic 0.1.37__tar.gz → 0.1.38__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.
Files changed (27) hide show
  1. {kbasic-0.1.37 → kbasic-0.1.38}/PKG-INFO +1 -1
  2. {kbasic-0.1.37 → kbasic-0.1.38}/pyproject.toml +1 -1
  3. kbasic-0.1.38/src/kbasic/parsing/__init__.py +6 -0
  4. kbasic-0.1.38/src/kbasic/parsing/basic.py +209 -0
  5. kbasic-0.1.38/src/kbasic/parsing/log.py +22 -0
  6. kbasic-0.1.38/src/kbasic/parsing/parser.py +31 -0
  7. kbasic-0.1.38/src/kbasic/parsing/toml.py +24 -0
  8. kbasic-0.1.38/src/kbasic/parsing/utils.py +27 -0
  9. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/vectors.py +18 -15
  10. kbasic-0.1.37/src/kbasic/parsing.py +0 -219
  11. {kbasic-0.1.37 → kbasic-0.1.38}/README.md +0 -0
  12. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/Tex.py +0 -0
  13. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/__init__.py +0 -0
  14. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/array.py +0 -0
  15. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/__init__.py +0 -0
  16. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/lib/Caroline Rose - year of the slug - 01 everything in its right place.wav +0 -0
  17. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/lib/success.mp3 +0 -0
  18. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/sound.py +0 -0
  19. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/bar.py +0 -0
  20. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/Keyan.py +0 -0
  21. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/__init__.py +0 -0
  22. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/anvil.py +0 -0
  23. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/defaultPC.py +0 -0
  24. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/shell.py +0 -0
  25. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/strings.py +0 -0
  26. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/typing.py +0 -0
  27. {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/user_input.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kbasic
3
- Version: 0.1.37
3
+ Version: 0.1.38
4
4
  Summary: Keyan's basic utility functions.
5
5
  Author: Keyan Gootkin
6
6
  Author-email: Keyan Gootkin <keyangootkin@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kbasic"
3
- version = "0.1.37"
3
+ version = "0.1.38"
4
4
  description = "Keyan's basic utility functions."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,6 @@
1
+ """
2
+ kbasic.parsing is a module designed to deal with i/o in an object oriented way.
3
+ """
4
+ from kbasic.parsing.utils import could_be_path, ensure_path
5
+ from kbasic.parsing.basic import File, Folder
6
+ from kbasic.parsing.parser import parse
@@ -0,0 +1,209 @@
1
+ """the bulk of kbasics path logic lives here."""
2
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
3
+ # >-|===|> Imports <|===|-<
4
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
5
+ from os import remove
6
+ from os.path import split, splitext, splitroot, exists, isdir, isfile
7
+ from shutil import copy, move, copytree, rmtree
8
+ from typing import Self, Optional
9
+ from glob import glob
10
+ from pathlib import Path as builtinPath
11
+ from kbasic.parsing.utils import ensure_path
12
+ from kbasic.user_input import yesno
13
+
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # >-|===|> Definitions <|===|-<
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ unreadable_file_types: list[str] = ['.gz', '.tar', '.zip']
18
+
19
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
20
+ # >-|===|> Classes <|===|-<
21
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
22
+ class File:
23
+ def __init__(
24
+ self, path: str | Self,
25
+ master: Optional[Self] = None, verbose: bool = False
26
+ ) -> None:
27
+ """A convenience class to deal with file io
28
+
29
+ Args:
30
+ path (str): the location of the file.
31
+ master (str, optional): the path to the template to restore this file
32
+ from if necessary. Defaults to None.
33
+ verbose (bool, optional): should this file be annoying. Defaults to
34
+ False.
35
+ """
36
+ if path is None: return None
37
+ if type(path)==type(self):
38
+ self = path
39
+ return None
40
+ self.path: str = str(builtinPath(path).resolve())
41
+ self.master = master if not isinstance(master, str) else File(master)
42
+ self.verbose = verbose
43
+ self.loaded: bool = False
44
+ parentpath, self.name = split(self.path)
45
+ self.title, self.extension = splitext(self.name)
46
+ self.drive, self.root, _ = splitroot(self.path)
47
+ self.parent = Folder(parentpath)
48
+ self.grandparent = self.parent.parent
49
+ self.greatgrandparent = self.parent.parent.parent
50
+ self.lines = []
51
+ def __repr__(self) -> str: return self.path
52
+ def __str__(self) -> str:
53
+ if not self.loaded: self.read()
54
+ return "\n".join(self.lines)
55
+ def __add__(self, other):
56
+ match other:
57
+ case list():
58
+ other.append(self)
59
+ return other
60
+ case File(): return [self, other]
61
+ case _:
62
+ raise NotImplementedError(
63
+ f"can't add object of type: {type(other)} to a Folder object: {repr(self)}"
64
+ )
65
+ def __radd__(self, other):
66
+ if other==0: return self
67
+ return self.__add__(other)
68
+ @property
69
+ def exists(self) -> bool:
70
+ """_summary_
71
+
72
+ Returns:
73
+ bool: _description_
74
+ """
75
+ return exists(self.path)
76
+ def copy(self, destination:str):
77
+ """_summary_
78
+
79
+ Args:
80
+ destination (str): _description_
81
+ """
82
+ copy(self.path, destination)
83
+ def move(self, destination:str):
84
+ """_summary_
85
+
86
+ Args:
87
+ destination (str): _description_
88
+ """
89
+ move(self.path, destination)
90
+ self = File(destination, master=self.master)
91
+ def update(self) -> None:
92
+ """_summary_
93
+ """
94
+ if self.verbose: print(f'updating {self.name}...')
95
+ assert self.master is not None, "No master copy to update from."
96
+ if self.exists: self.delete(interactive=False)
97
+ self.master.copy(self.path)
98
+ self = File(self.path, master=self.master)
99
+ def delete(self, interactive=True) -> None:
100
+ """summary"""
101
+ if interactive and not yesno(
102
+ f"Are you sure you want to permanently delete {self.path} and all of its contents?\n"
103
+ ):
104
+ return None
105
+ remove(self.path)
106
+ def read(self) -> None:
107
+ """summary"""
108
+ if not self.exists or self.extension in unreadable_file_types: return []
109
+ with open(self.path, 'r') as file:
110
+ self.lines = [f.strip('\n') for f in file.readlines()]
111
+ self.loaded = True
112
+ def load(self) -> None:
113
+ """docstring"""
114
+ self.read()
115
+ def save(self, interactive=True):
116
+ """summary"""
117
+ if interactive and not yesno(
118
+ f"Are you sure you want to permanently overwrite {self.path}?\n"
119
+ ):
120
+ return None
121
+ with open(self.path, 'w+') as file:
122
+ if not file.writable:
123
+ raise PermissionError(
124
+ f"attempted to save unwritable file: {self.path}"
125
+ )
126
+ file.writelines("\n".join(self.lines))
127
+
128
+ class Folder:
129
+ def __init__(
130
+ self, path: str|Self,
131
+ master: Optional[Self] = None
132
+ ) -> None:
133
+ if type(path)==type(self):
134
+ path = path.path
135
+ self.path = str()
136
+ self.master = master if not isinstance(master, str) else Folder(master)
137
+ self.parentpath, self.name = split(self.path)
138
+ def __repr__(self) -> str: return self.path
139
+ def __len__(self) -> int: return len(self.children)
140
+ def __iter__(self):
141
+ self.index = 0
142
+ return self
143
+ def __next__(self):
144
+ if self.index < len(self):
145
+ i = self.index
146
+ self.index += 1
147
+ return Folder(self.children[i]) if isdir(self.children[i]) else File(self.children[i])
148
+ raise StopIteration
149
+ def __add__(self, other):
150
+ match other:
151
+ case str():
152
+ p = f"{self.path}/{other}"
153
+ return Path(p)
154
+ case File(): return self.children+[other]
155
+ case Folder(): return self.children + other.children
156
+ case list(): return self.children + other
157
+ case _:
158
+ raise NotImplementedError(
159
+ f"can't add object of type: {type(other)} to a Folder object: {repr(self)}"
160
+ )
161
+ def __radd__(self, other):
162
+ if other==0: return [self]
163
+ return self.__add__(other)
164
+ @property
165
+ def parent(self) -> Self:
166
+ """summary"""
167
+ return Folder(self.parentpath)
168
+ @property
169
+ def exists(self) -> bool:
170
+ """summary"""
171
+ return exists(self.path)
172
+ def glob(self, pattern: str):
173
+ """summary"""
174
+ return glob(self.path+pattern)
175
+ @property
176
+ def children(self) -> list:
177
+ """summary"""
178
+ return self.glob("/*")
179
+ def ls(self) -> None:
180
+ """summary"""
181
+ print("\n".join(self.children))
182
+ def make(self) -> None:
183
+ """summary"""
184
+ ensure_path(self.path)
185
+ def copy(self, destination:str) -> None:
186
+ """summary"""
187
+ copytree(self.path, destination)
188
+ def revert(self) -> None:
189
+ """summary"""
190
+ assert self.master is not None, "No master copy to update from."
191
+ if self.exists: self.delete(interactive=False)
192
+ self.master.copy(self.path)
193
+ self = Folder(self.path, master=self.master)
194
+ def delete(self, interactive=True) -> None:
195
+ """summary"""
196
+ if interactive and not yesno(
197
+ f"Are you sure you want to permanently delete {self.path} and all of its contents?\n"
198
+ ):
199
+ return None
200
+ rmtree(self.path)
201
+
202
+ class Path:
203
+ """
204
+ A constructor to return either a kbasic.Folder, kbasic.File, or a pathlib.Path.
205
+ """
206
+ def __new__(cls, path: str, *args, **kwds):
207
+ if isdir(path): return Folder(path, *args, **kwds)
208
+ if isfile(path): return File(path, *args, **kwds)
209
+ return builtinPath(path)
@@ -0,0 +1,22 @@
1
+ """A logger!"""
2
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
3
+ # >-|===|> Imports <|===|-<
4
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
5
+ from typing import Callable
6
+ from datetime import datetime
7
+ from kbasic.parsing.basic import File
8
+
9
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
+ # >-|===|> Functions <|===|-<
11
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
12
+ def default_log_formatter(msg: str) -> str:
13
+ """prefix datetime.now on all log entries"""
14
+ return f"{str(datetime.now())}: {msg}"
15
+
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ # >-|===|> Classes <|===|-<
18
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
19
+ class Log(File):
20
+ def __init__(self, path: str, verbose: bool = False) -> None:
21
+ File.__init__(self, path, verbose=verbose)
22
+ def __call__(self, msg: str, fmt: Callable[[str], str] = default_log_formatter) -> None: pass
@@ -0,0 +1,31 @@
1
+ """a handler to take a path and deliver the correct object to the user"""
2
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
3
+ # >-|===|> Imports <|===|-<
4
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
5
+ from os.path import isfile, isdir
6
+ from kbasic.typing import Array
7
+ from kbasic.parsing.basic import File, Folder
8
+ from kbasic.parsing.toml import TOML
9
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
+ # >-|===|> Functions <|===|-<
11
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
12
+ def parse(path: str | list[str]) -> Folder | File | list[Folder | File]:
13
+ """take a path or list of paths and turn them into Folder or File objects as appropriate.
14
+
15
+ Args:
16
+ path (str | list[str]): the path you want to be a File/Folder object
17
+
18
+ Raises:
19
+ FileNotFoundError: if you can't match path
20
+
21
+ Returns:
22
+ Folder | File | list[Folder|File]: path(s) as a Folder/File.
23
+ """
24
+ match path:
25
+ case str():
26
+ if isdir(path): return Folder(path)
27
+ if isfile(path):
28
+ return TOML(path) if path.split('.')[-1]=='toml' else File(path)
29
+ case _ if type(path) in Array.types:
30
+ return [Folder(p) if isdir(p) else File(p) if isfile(p) else None for p in path]
31
+ raise FileNotFoundError(f"Unable to parse path(s): {path}")
@@ -0,0 +1,24 @@
1
+ """implement a toml class that loads the variables into its __dict__"""
2
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
3
+ # >-|===|> Imports <|===|-<
4
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
5
+ from tomllib import load as tomlload
6
+ from kbasic.parsing.basic import File
7
+
8
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
9
+ # >-|===|> Classes <|===|-<
10
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ class TOML(File):
12
+ def __init__(
13
+ self, path: str,
14
+ verbose: bool = False
15
+ ) -> None:
16
+ File.__init__(self, path, verbose=verbose)
17
+ if self.exists: self.read()
18
+ def read(self):
19
+ """summary"""
20
+ self.loaded = True
21
+ with open(self.path, 'rb') as f:
22
+ self._attrs = tomlload(f)
23
+ self.__dict__ |= self._attrs
24
+ self.lines = [f"{k}={v}" for k,v in self._attrs.items()]
@@ -0,0 +1,27 @@
1
+ """basic utility functions for kbasic.parsing, mostly from os.path"""
2
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
3
+ # >-|===|> Imports <|===|-<
4
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
5
+ from os import system
6
+ from os.path import isdir
7
+
8
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
9
+ # >-|===|> Functions <|===|-<
10
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ def ensure_path(path: str) -> None:
12
+ """make sure that a path exists
13
+
14
+ Args:
15
+ path (str): the path you ant to exist
16
+ """
17
+ system(f"mkdir -p {path}")
18
+ def could_be_path(path: str) -> bool:
19
+ """determine if this is anywhere close to a valid path
20
+
21
+ Args:
22
+ path (str): the path (?) to check
23
+
24
+ Returns:
25
+ bool: True if the first two members of the path are valid else False.
26
+ """
27
+ return isdir('/'.join(path.split('/')[:3]))
@@ -1,10 +1,10 @@
1
1
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
2
  # >-|===|> Imports <|===|-<
3
3
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- from numpy import array, cos, sin, sqrt, exp
5
4
  from typing import Self
6
5
  from collections.abc import Generator
7
- from kbasic.typing import Number, ArrayLike
6
+ from numpy import array, cos, sin, sqrt, exp
7
+ from kbasic.typing import Number, Array
8
8
 
9
9
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
10
  # >-|===|> Definitions <|===|-<
@@ -13,7 +13,7 @@ from kbasic.typing import Number, ArrayLike
13
13
  # >-|===|> Functions <|===|-<
14
14
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
15
  def hamilton_product(q1, q2):
16
- a1, b1, c1, d1 = q1
16
+ a1, b1, c1, d1 = q1
17
17
  a2, b2, c2, d2 = q2
18
18
  return array([
19
19
  a1*a2 - b1*b2 - c1*c2 - d1*d2,
@@ -24,7 +24,7 @@ def hamilton_product(q1, q2):
24
24
  def organize_components(components) -> tuple[float|int]:
25
25
  match components:
26
26
  case (x, *_) if type(x) in Number.types: return components
27
- case (x,) if type(x) in ArrayLike.types: return tuple(x)
27
+ case (x,) if type(x) in Array.types: return tuple(x)
28
28
  case (Generator(),): return tuple(components[0])
29
29
  case (Vector(),): return components[0].components
30
30
  case _: raise TypeError(f"{components} cannot be matched")
@@ -36,11 +36,11 @@ class Vector:
36
36
  def __init__(self, *components) -> None:
37
37
  self.components = organize_components(components)
38
38
  self.ndims = self.dim = self.dimensions = len(components) #i always forget which one i choose :)
39
- def __repr__(self) -> str:
39
+ def __repr__(self) -> str:
40
40
  return str(self.components)
41
- def __len__(self) -> int:
41
+ def __len__(self) -> int:
42
42
  return self.dimensions
43
- def __abs__(self) -> Number:
43
+ def __abs__(self) -> Number:
44
44
  return sqrt(sum(array(self.components)**2))
45
45
  def __iter__(self) -> Self:
46
46
  self._index = 0
@@ -50,16 +50,16 @@ class Vector:
50
50
  value = self.components[self._index]
51
51
  self._index += 1
52
52
  return value
53
- except IndexError: raise StopIteration
53
+ except IndexError as err: raise StopIteration from err
54
54
  def __add__(self, other) -> Self:
55
55
  match other:
56
56
  case Vector(): return type(self)(xs+xo for xs, xo in zip(self.components, other.components))
57
57
  case x if type(x) in Number.types: return type(self)(x+other for x in self.components)
58
- case x if type(x) in ArrayLike.types: return type(self)(xs+xo for xs, xo in zip(self.components, other))
58
+ case x if type(x) in Array.types: return type(self)(xs+xo for xs, xo in zip(self.components, other))
59
59
  def __sub__(self, other) -> Self:
60
60
  match other:
61
61
  case x if type(x) in Number.types: return type(self)(x-other for x in self.components)
62
- case x if type(x) in ArrayLike.types: return type(self)(xs-xo for xs, xo in zip(self.components, other))
62
+ case x if type(x) in Array.types: return type(self)(xs-xo for xs, xo in zip(self.components, other))
63
63
  case Vector(): return type(self)(xs-xo for xs, xo in zip(self.components, other.components))
64
64
  def __mul__(self, other) -> Self|Number:
65
65
  match other:
@@ -113,7 +113,7 @@ class R2(Vector):
113
113
  def rotate(self, angle):
114
114
  r = self.x + 1j*self.y
115
115
  r_rotated = r * exp(1j*angle)
116
- self.__init__(r_rotated.real, r_rotated.imag)
116
+ self = R2(r_rotated.real, r_rotated.imag)
117
117
  class R3(Vector):
118
118
  def __init__(self, *components):
119
119
  self.components: tuple[Number] = organize_components(components)
@@ -132,11 +132,14 @@ class R3(Vector):
132
132
  rotation = array(Norm([cos(angle/2), *(axis*sin(angle/2))]).components)
133
133
  rotation_inverse = array([rotation[0], *-rotation[1:]])
134
134
  rotated_vector = hamilton_product(hamilton_product(rotation, qs), rotation_inverse)[1:]
135
- self.__init__(*rotated_vector)
135
+ self = R3(*rotated_vector)
136
136
  class Matrix:
137
- def __init__(self, array):
138
- self.array = array
137
+ def __init__(self, arr):
138
+ self.array = arr
139
139
  def __mul__(self, other):
140
140
  match other:
141
141
  case Vector():
142
- res = [[self.array[i,j]*other.components[i] for i in range(len(other))] for j in range(len(other))]
142
+ res = [
143
+ [self.array[i,j]*other.components[i] for i in range(len(other))]
144
+ for j in range(len(other))]
145
+ return res
@@ -1,219 +0,0 @@
1
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
- # >-|===|> Imports <|===|-<
3
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- #pysim imports
5
- from kbasic.user_input import yesno
6
- from kbasic.typing import Array, Number
7
- #nonpysim imports
8
- from time import time
9
- from datetime import datetime
10
- from typing import Self, Optional, Callable
11
- from numpy import ndarray
12
- from glob import glob
13
- from shutil import copy, move, copytree, rmtree
14
- from os.path import isdir, isfile, exists, abspath, expanduser, expandvars, \
15
- normpath, splitroot, split, splitext
16
- from os import system, remove
17
- import tomllib
18
-
19
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
20
- # >-|===|> Definitions <|===|-<
21
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
22
- unreadable_file_types: list[str] = ['.gz', '.tar', '.zip']
23
-
24
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
25
- # >-|===|> Functions <|===|-<
26
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
27
- def clean_path(path: str) -> str:
28
- """return an absolute, normalzed, cleaned path with expanded environment
29
- variables and user characters
30
-
31
- Args:
32
- path (str): input path sting
33
-
34
- Returns:
35
- str: a path
36
- """
37
- return normpath(expanduser(expandvars(path)))
38
- def ensure_path(path: str) -> None:
39
- """make sure that a path exiss
40
-
41
- Args:
42
- path (str): the path you ant to exist
43
- """
44
- system(f"mkdir -p {path}")
45
- def could_be_path(path: str) -> bool:
46
- """determine if this is anywhere close to a valid path
47
-
48
- Args:
49
- path (str): the path (?) to check
50
-
51
- Returns:
52
- bool: True if the first two members of the path are valid else False.
53
- """
54
- return isdir('/'.join(path.split('/')[:2]))
55
- def default_log_formater(msg: str) -> str:
56
- return f"{str(datetime.now())}: {msg}"
57
- def delta_log_formater(msg: str, start_time: Number) -> str:
58
- return f"Δt: {(time()-start_time)*1e6:.1f} μs | {msg}"
59
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
60
- # >-|===|> Classes <|===|-<
61
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
62
- class File:
63
- def __init__(self, path: str|Self, master=None, verbose:bool=False) -> None:
64
- """A convenience class to deal with file io
65
-
66
- Args:
67
- path (str): the location of the file.
68
- master (str, optional): the path to the template to restore this file
69
- from if necessary. Defaults to None.
70
- verbose (bool, optional): should this file be annoying. Defaults to
71
- False.
72
- """
73
- if path is None: return None
74
- elif type(path)==type(self):
75
- self = path
76
- return None
77
- self.path: str = clean_path(path)
78
- self.master = master if not isinstance(master, str) else File(master)
79
- self.verbose = verbose
80
- parentpath, self.name = split(self.path)
81
- self.title, self.extension = splitext(self.name)
82
- self.drive, self.root, tail = splitroot(self.path)
83
- self.parent = Folder(parentpath)
84
- self.grandparent = self.parent.parent
85
- self.greatgrandparent = self.parent.parent.parent
86
- self.lines = []
87
- def __repr__(self) -> str: return self.path
88
- def __str__(self) -> str: return "\n".join(self.lines)
89
- def __add__(self, other):
90
- match other:
91
- case list():
92
- other.append(self)
93
- return other
94
- case File(): return [self, other]
95
-
96
- case _: raise NotImplementedError(f"can't add object of type: {type(other)} to a Folder object: {repr(self)}")
97
- def __radd__(self, other):
98
- if other==0: return self
99
- return self.__add__(other)
100
- @property
101
- def exists(self) -> bool: return exists(self.path)
102
- def copy(self, destination:str): copy(self.path, destination)
103
- def move(self, destination:str):
104
- move(self.path, destination)
105
- self = File.__init__(destination, master=self.master)
106
- def update(self) -> None:
107
- if self.verbose: print(f'updating {self.name}...')
108
- assert self.master is not None, "No master copy to update from."
109
- if self.exists: self.delete(interactive=False)
110
- self.master.copy(self.path)
111
- self = File(self.path, master=self.master)
112
- def delete(self, interactive=True) -> None:
113
- if interactive and not yesno(f"Are you sure you want to permanently delete {self.path} and all of its contents?\n"): return None
114
- else: remove(self.path)
115
- def read(self) -> None:
116
- if not self.exists: return []
117
- with open(self.path, 'r') as file:
118
- self.lines = [f.strip('\n') for f in file.readlines()]
119
- def save(self, interactive=True):
120
- if interactive and not yesno(f"Are you sure you want to permanently overwrite {self.path}?\n"):
121
- return None
122
- with open(self.path, 'w+') as file:
123
- if not file.writable: raise PermissionError(f"attempted to save unwritable file: {self.path}")
124
- file.writelines("\n".join(self.lines))
125
- class Log(File):
126
- def __init__(self, path: str | Self, verbose: bool = False, fmt: Callable[[str], str] = default_log_formater):
127
- File.__init__(self, path, verbose=verbose)
128
- self.fmt = fmt
129
- def __call__(self, msg: Array|str, *args, **kwds):
130
- match msg:
131
- case str(): output = [self.fmt(msg, *args, **kwds)]
132
- case _ if type(msg) in Array.types: output = [self.fmt(m, *args, **kwds) for m in msg]
133
- if self.verbose: print(output)
134
- self.lines += output
135
- self.save(interactive=False)
136
- class TOML(File):
137
- def __init__(self, path: str, verbose: bool = False):
138
- File.__init__(self, path, verbose=verbose)
139
- self.loaded: bool = False
140
- if self.exists: self.read()
141
- def read(self):
142
- self.loaded = True
143
- self._attrs = tomllib.load(open(self.path, 'rb'))
144
- self.__dict__ |= self._attrs
145
- self.lines = [f"{k}={v}" for k,v in self._attrs.items()]
146
- class Folder:
147
- def __init__(self, path: str|Self, master: Optional[Self]=None) -> None:
148
- if type(path)==type(self):
149
- self.__init__(path.path)
150
- return None
151
- self.path = clean_path(path)
152
- self.master = master if not isinstance(master, str) else Folder(master)
153
- self.parentpath, self.name = split(self.path)
154
- def __repr__(self) -> str: return self.path
155
- def __len__(self) -> int: return len(self.children)
156
- def __iter__(self):
157
- self.index = 0
158
- return self
159
- def __next__(self):
160
- if self.index < len(self):
161
- i = self.index
162
- self.index += 1
163
- return Folder(self.children[i]) if isdir(self.children[i]) else File(self.children[i])
164
- else: raise StopIteration
165
- def __add__(self, other):
166
- match other:
167
- case str():
168
- p = f"{self.path}/{other}"
169
- return Folder(p) if isdir(p) else File(p) if isfile(p) else None
170
- case File(): return self.children+[other]
171
- case Folder(): return self.children + other.children
172
- case list(): return self.children + other
173
- case _: raise NotImplementedError(f"can't add object of type: {type(other)} to a Folder object: {repr(self)}")
174
- def __radd__(self, other):
175
- if other==0: return [self]
176
- return self.__add__(other)
177
- @property
178
- def parent(self) -> Self:
179
- return Folder(self.parentpath)
180
- @property
181
- def exists(self) -> bool: return exists(self.path)
182
- @property
183
- def children(self) -> list: return glob(self.path+"/*" if self.path!='/' else "/*")
184
- def ls(self) -> None: print("\n".join(self.children))
185
- def make(self) -> None: ensure_path(self.path)
186
- def copy(self, destination:str) -> None: copytree(self.path, destination)
187
- def revert(self) -> None:
188
- assert self.master is not None, "No master copy to update from."
189
- if self.exists: self.delete(interactive=False)
190
- self.master.copy(self.path)
191
- self = Folder(self.path, master=self.master)
192
- def delete(self, interactive=True) -> None:
193
- if interactive and not yesno(f"Are you sure you want to permanently delete {self.path} and all of its contents?\n"):
194
- return None
195
- rmtree(self.path)
196
-
197
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
198
- # >-|===|> Functions <|===|-<
199
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
200
- def parse(path: str | list[str]) -> Folder | File | list[Folder | File]:
201
- """take a path or list of paths and turn them into Folder or File objects as appropriate.
202
-
203
- Args:
204
- path (str | list[str]): the path you want to be a File/Folder object
205
-
206
- Raises:
207
- FileNotFoundError: if you can't match path
208
-
209
- Returns:
210
- Folder | File | list[Folder|File]: path(s) as a Folder/File.
211
- """
212
- match path:
213
- case str():
214
- if isdir(path): return Folder(path)
215
- if isfile(path):
216
- return TOML(path) if path.split('.')[-1]=='toml' else File(path)
217
- case list() | ndarray():
218
- return [Folder(p) if isdir(p) else File(p) if isfile(p) else None for p in path]
219
- raise FileNotFoundError(f"Unable to parse path(s): {path}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes