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.
- {kbasic-0.1.37 → kbasic-0.1.38}/PKG-INFO +1 -1
- {kbasic-0.1.37 → kbasic-0.1.38}/pyproject.toml +1 -1
- kbasic-0.1.38/src/kbasic/parsing/__init__.py +6 -0
- kbasic-0.1.38/src/kbasic/parsing/basic.py +209 -0
- kbasic-0.1.38/src/kbasic/parsing/log.py +22 -0
- kbasic-0.1.38/src/kbasic/parsing/parser.py +31 -0
- kbasic-0.1.38/src/kbasic/parsing/toml.py +24 -0
- kbasic-0.1.38/src/kbasic/parsing/utils.py +27 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/vectors.py +18 -15
- kbasic-0.1.37/src/kbasic/parsing.py +0 -219
- {kbasic-0.1.37 → kbasic-0.1.38}/README.md +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/Tex.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/__init__.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/array.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/__init__.py +0 -0
- {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
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/lib/success.mp3 +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/audio/sound.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/bar.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/Keyan.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/__init__.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/anvil.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/environment/defaultPC.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/shell.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/strings.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/typing.py +0 -0
- {kbasic-0.1.37 → kbasic-0.1.38}/src/kbasic/user_input.py +0 -0
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
135
|
+
self = R3(*rotated_vector)
|
|
136
136
|
class Matrix:
|
|
137
|
-
def __init__(self,
|
|
138
|
-
self.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 = [
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|