pysfi 0.1.10__py3-none-any.whl → 0.1.11__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.
- {pysfi-0.1.10.dist-info → pysfi-0.1.11.dist-info}/METADATA +7 -7
- pysfi-0.1.11.dist-info/RECORD +60 -0
- {pysfi-0.1.10.dist-info → pysfi-0.1.11.dist-info}/entry_points.txt +12 -2
- sfi/__init__.py +1 -1
- sfi/alarmclock/alarmclock.py +40 -40
- sfi/bumpversion/__init__.py +1 -1
- sfi/cleanbuild/cleanbuild.py +155 -0
- sfi/condasetup/condasetup.py +116 -0
- sfi/docscan/__init__.py +1 -1
- sfi/docscan/docscan_gui.py +1 -1
- sfi/docscan/lang/eng.py +152 -152
- sfi/docscan/lang/zhcn.py +170 -170
- sfi/filedate/filedate.py +185 -112
- sfi/gittool/__init__.py +2 -0
- sfi/gittool/gittool.py +401 -0
- sfi/llmclient/llmclient.py +592 -0
- sfi/llmquantize/llmquantize.py +480 -0
- sfi/llmserver/llmserver.py +335 -0
- sfi/makepython/makepython.py +2 -2
- sfi/pdfsplit/pdfsplit.py +4 -4
- sfi/pyarchive/pyarchive.py +418 -0
- sfi/pyembedinstall/pyembedinstall.py +629 -0
- sfi/pylibpack/pylibpack.py +813 -269
- sfi/pylibpack/rules/numpy.json +22 -0
- sfi/pylibpack/rules/pymupdf.json +10 -0
- sfi/pylibpack/rules/pyqt5.json +19 -0
- sfi/pylibpack/rules/pyside2.json +23 -0
- sfi/pylibpack/rules/scipy.json +23 -0
- sfi/pylibpack/rules/shiboken2.json +24 -0
- sfi/pyloadergen/pyloadergen.py +271 -572
- sfi/pypack/pypack.py +822 -471
- sfi/pyprojectparse/__init__.py +0 -0
- sfi/pyprojectparse/pyprojectparse.py +500 -0
- sfi/pysourcepack/pysourcepack.py +308 -369
- sfi/quizbase/__init__.py +0 -0
- sfi/quizbase/quizbase.py +828 -0
- sfi/quizbase/quizbase_gui.py +987 -0
- sfi/regexvalidate/__init__.py +0 -0
- sfi/regexvalidate/regex_help.html +284 -0
- sfi/regexvalidate/regexvalidate.py +468 -0
- sfi/taskkill/taskkill.py +0 -2
- pysfi-0.1.10.dist-info/RECORD +0 -39
- sfi/embedinstall/embedinstall.py +0 -478
- sfi/projectparse/projectparse.py +0 -152
- {pysfi-0.1.10.dist-info → pysfi-0.1.11.dist-info}/WHEEL +0 -0
- /sfi/{embedinstall → llmquantize}/__init__.py +0 -0
- /sfi/{projectparse → pyembedinstall}/__init__.py +0 -0
sfi/gittool/__init__.py
ADDED
sfi/gittool/gittool.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import platform
|
|
6
|
+
import subprocess
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from functools import cached_property
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(level=logging.INFO)
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
is_windows = platform.system() == "Windows"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_git_status() -> bool:
|
|
18
|
+
"""Check if there are uncommitted changes.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
bool: Whether there are uncommitted changes
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
["git", "status", "--porcelain"],
|
|
26
|
+
capture_output=True,
|
|
27
|
+
text=True,
|
|
28
|
+
check=False,
|
|
29
|
+
)
|
|
30
|
+
except subprocess.CalledProcessError as e:
|
|
31
|
+
logger.error(f"Git status failed: {e}")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
if result.stdout.strip():
|
|
35
|
+
logger.error(
|
|
36
|
+
f"Uncommitted changes exist, please commit changes first: {result.stdout}"
|
|
37
|
+
)
|
|
38
|
+
return False
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class GitInitCommand:
|
|
44
|
+
"""Git init command data class."""
|
|
45
|
+
|
|
46
|
+
SUBCOMMANDS: ClassVar[list[list[str]]] = [
|
|
47
|
+
["git", "init"],
|
|
48
|
+
["git", "add", "."],
|
|
49
|
+
["git", "commit", "-m", "initial commit"],
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
def run(self) -> None:
|
|
53
|
+
"""Run git init command."""
|
|
54
|
+
for subcommand in self.SUBCOMMANDS:
|
|
55
|
+
try:
|
|
56
|
+
subprocess.run(subcommand, check=True)
|
|
57
|
+
logger.info(f"Executed: {' '.join(subcommand)}")
|
|
58
|
+
except subprocess.CalledProcessError as e:
|
|
59
|
+
logger.error(f"Git init failed: {e}")
|
|
60
|
+
return
|
|
61
|
+
logger.info("Git initialization completed successfully")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class GitAddCommand:
|
|
66
|
+
"""Git add command data class."""
|
|
67
|
+
|
|
68
|
+
def run(self) -> None:
|
|
69
|
+
"""Run git add command."""
|
|
70
|
+
change_files_status = {
|
|
71
|
+
"Added": self.added_filenames,
|
|
72
|
+
"Modified": self.modified_filenames,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for change_type, files in change_files_status.items():
|
|
76
|
+
if files:
|
|
77
|
+
file_str = ", ".join(files)
|
|
78
|
+
logger.info(f"{change_type} files: {file_str}")
|
|
79
|
+
try:
|
|
80
|
+
subprocess.run(
|
|
81
|
+
["git", "commit", "-m", f"{change_type} files: {file_str}"],
|
|
82
|
+
check=True,
|
|
83
|
+
)
|
|
84
|
+
except subprocess.CalledProcessError as e:
|
|
85
|
+
logger.error(f"Git commit failed: {e}")
|
|
86
|
+
return
|
|
87
|
+
else:
|
|
88
|
+
logger.warning(f"No {change_type} files")
|
|
89
|
+
|
|
90
|
+
@cached_property
|
|
91
|
+
def modified_filenames(self) -> set[str]:
|
|
92
|
+
"""Get list of modified files from git status."""
|
|
93
|
+
return {f.filepath.stem for f in self.added_filestatus if f.status == "M"}
|
|
94
|
+
|
|
95
|
+
@cached_property
|
|
96
|
+
def added_filenames(self) -> set[str]:
|
|
97
|
+
"""Get list of added files from git status."""
|
|
98
|
+
return {f.filepath.stem for f in self.added_filestatus if f.status == "A"}
|
|
99
|
+
|
|
100
|
+
@cached_property
|
|
101
|
+
def added_filestatus(self) -> set[GitAddFileStatus]:
|
|
102
|
+
"""Get list of added files from git status."""
|
|
103
|
+
before = self.changed_files_info
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
subprocess.run(["git", "add", "."], check=True)
|
|
107
|
+
except subprocess.CalledProcessError as e:
|
|
108
|
+
logger.error(f"Git add failed: {e}")
|
|
109
|
+
return set()
|
|
110
|
+
|
|
111
|
+
after = self.changed_files_info
|
|
112
|
+
return {f for f in after - before if f.status == "A"}
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def changed_files_info(self) -> set[GitAddFileStatus]:
|
|
116
|
+
"""Get list of changed files from git status."""
|
|
117
|
+
result = subprocess.run(
|
|
118
|
+
["git", "status", "--porcelain"],
|
|
119
|
+
capture_output=True,
|
|
120
|
+
text=True,
|
|
121
|
+
check=False,
|
|
122
|
+
)
|
|
123
|
+
files: set[GitAddFileStatus] = set()
|
|
124
|
+
if result.returncode == 0:
|
|
125
|
+
for line in result.stdout.splitlines():
|
|
126
|
+
if line.strip():
|
|
127
|
+
status = line[:2].strip()
|
|
128
|
+
filename = line[3:].strip()
|
|
129
|
+
files.add(GitAddFileStatus(status, Path(filename)))
|
|
130
|
+
return files
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class GitAddFileStatus:
|
|
135
|
+
"""Git file status data class.
|
|
136
|
+
|
|
137
|
+
Properties:
|
|
138
|
+
status: File status, A: Added, M: Modified
|
|
139
|
+
filepath: File path
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
status: str
|
|
143
|
+
filepath: Path
|
|
144
|
+
|
|
145
|
+
def __hash__(self) -> int:
|
|
146
|
+
"""Calculate hash value for unique identification in sets.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
int: Hash value
|
|
150
|
+
"""
|
|
151
|
+
return hash((self.status, str(self.filepath)))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class GitCleanCommand:
|
|
156
|
+
"""Git clean command."""
|
|
157
|
+
|
|
158
|
+
EXCLUDE_DIRS: ClassVar[list[str]] = [
|
|
159
|
+
".venv",
|
|
160
|
+
"node_modules",
|
|
161
|
+
".git",
|
|
162
|
+
".idea",
|
|
163
|
+
".vscode",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
def run(self, force: bool = False) -> None:
|
|
167
|
+
"""Run git clean command.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
force: Force clean without confirmation
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
cmd = ["git", "clean", "-xd"]
|
|
174
|
+
for exclude_dir in self.EXCLUDE_DIRS:
|
|
175
|
+
cmd.extend(["-e", exclude_dir])
|
|
176
|
+
|
|
177
|
+
if force:
|
|
178
|
+
cmd.append("-f")
|
|
179
|
+
|
|
180
|
+
subprocess.run(cmd, check=True)
|
|
181
|
+
subprocess.run(["git", "checkout", "."], check=True)
|
|
182
|
+
logger.info("Clean completed successfully")
|
|
183
|
+
except subprocess.CalledProcessError as e:
|
|
184
|
+
logger.error(f"Git clean failed: {e}")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class GitPushCommand:
|
|
190
|
+
"""Git push command."""
|
|
191
|
+
|
|
192
|
+
def run(self, all_branches: bool = False) -> None:
|
|
193
|
+
"""Run git push command.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
all_branches: Push all branches
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
if all_branches:
|
|
200
|
+
subprocess.run(["git", "push", "--all"], check=True)
|
|
201
|
+
logger.info("Push all branches completed successfully")
|
|
202
|
+
else:
|
|
203
|
+
subprocess.run(["git", "push"], check=True)
|
|
204
|
+
logger.info("Push completed successfully")
|
|
205
|
+
except subprocess.CalledProcessError as e:
|
|
206
|
+
logger.error(f"Git push failed: {e}")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass
|
|
211
|
+
class GitPullCommand:
|
|
212
|
+
"""Git pull command."""
|
|
213
|
+
|
|
214
|
+
def run(self, rebase: bool = False) -> None:
|
|
215
|
+
"""Run git pull command.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
rebase: Use rebase when pulling
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
if rebase:
|
|
222
|
+
subprocess.run(["git", "pull", "--rebase"], check=True)
|
|
223
|
+
logger.info("Pull with rebase completed successfully")
|
|
224
|
+
else:
|
|
225
|
+
subprocess.run(["git", "pull"], check=True)
|
|
226
|
+
logger.info("Pull completed successfully")
|
|
227
|
+
except subprocess.CalledProcessError as e:
|
|
228
|
+
logger.error(f"Git pull failed: {e}")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class GitCheckoutCommand:
|
|
234
|
+
"""Git checkout command."""
|
|
235
|
+
|
|
236
|
+
def run(self, branch: str, create: bool = False) -> None:
|
|
237
|
+
"""Run git checkout command.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
branch: Branch name to checkout
|
|
241
|
+
create: Create new branch if True
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
if create:
|
|
245
|
+
subprocess.run(["git", "checkout", "-b", branch], check=True)
|
|
246
|
+
logger.info(f"Created and checked out branch: {branch}")
|
|
247
|
+
else:
|
|
248
|
+
subprocess.run(["git", "checkout", branch], check=True)
|
|
249
|
+
logger.info(f"Checked out branch: {branch}")
|
|
250
|
+
except subprocess.CalledProcessError as e:
|
|
251
|
+
logger.error(f"Git checkout failed: {e}")
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@dataclass
|
|
256
|
+
class GitBranchCommand:
|
|
257
|
+
"""Git branch command."""
|
|
258
|
+
|
|
259
|
+
def run(self, list_all: bool = False, delete: str | None = None) -> None:
|
|
260
|
+
"""Run git branch command.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
list_all: List all branches (remote and local)
|
|
264
|
+
delete: Branch name to delete
|
|
265
|
+
"""
|
|
266
|
+
try:
|
|
267
|
+
if delete:
|
|
268
|
+
subprocess.run(["git", "branch", "-d", delete], check=True)
|
|
269
|
+
logger.info(f"Deleted branch: {delete}")
|
|
270
|
+
elif list_all:
|
|
271
|
+
result = subprocess.run(
|
|
272
|
+
["git", "branch", "-a"],
|
|
273
|
+
capture_output=True,
|
|
274
|
+
text=True,
|
|
275
|
+
check=True,
|
|
276
|
+
)
|
|
277
|
+
logger.info("All branches:")
|
|
278
|
+
logger.info(result.stdout)
|
|
279
|
+
else:
|
|
280
|
+
result = subprocess.run(
|
|
281
|
+
["git", "branch"],
|
|
282
|
+
capture_output=True,
|
|
283
|
+
text=True,
|
|
284
|
+
check=True,
|
|
285
|
+
)
|
|
286
|
+
logger.info("Local branches:")
|
|
287
|
+
logger.info(result.stdout)
|
|
288
|
+
except subprocess.CalledProcessError as e:
|
|
289
|
+
logger.error(f"Git branch command failed: {e}")
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass
|
|
294
|
+
class GitRestartTGitCacheCommand:
|
|
295
|
+
"""Git restart command."""
|
|
296
|
+
|
|
297
|
+
def run(self) -> None:
|
|
298
|
+
"""Run git restart command."""
|
|
299
|
+
logger.info("Restarting tgitcache...")
|
|
300
|
+
cmds = (
|
|
301
|
+
["taskkill", "/f", "/t", "/im", "tgitcache.exe"]
|
|
302
|
+
if is_windows
|
|
303
|
+
else ["killall", "tgitcache"]
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
subprocess.run(cmds, check=True)
|
|
308
|
+
logger.info("Restart completed successfully")
|
|
309
|
+
except subprocess.CalledProcessError as e:
|
|
310
|
+
logger.error(f"Git restart failed: {e}")
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
_COMMAND_MAP = {
|
|
315
|
+
"i": GitInitCommand,
|
|
316
|
+
"a": GitAddCommand,
|
|
317
|
+
"c": GitCleanCommand,
|
|
318
|
+
"p": GitPushCommand,
|
|
319
|
+
"pl": GitPullCommand,
|
|
320
|
+
"co": GitCheckoutCommand,
|
|
321
|
+
"b": GitBranchCommand,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def parse_args() -> argparse.Namespace:
|
|
326
|
+
"""Parse command line arguments.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
argparse.Namespace: Parsed arguments
|
|
330
|
+
"""
|
|
331
|
+
parser = argparse.ArgumentParser(description="Gittool - Git command wrapper")
|
|
332
|
+
subparsers = parser.add_subparsers(
|
|
333
|
+
dest="command", help="Available commands", required=True
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Init subcommand
|
|
337
|
+
subparsers.add_parser("i", help="Initialize git repository")
|
|
338
|
+
|
|
339
|
+
# Add subcommand
|
|
340
|
+
subparsers.add_parser("a", help="Stage and commit changes")
|
|
341
|
+
|
|
342
|
+
# Clean subcommand
|
|
343
|
+
parser_c = subparsers.add_parser("c", help="Clean untracked files")
|
|
344
|
+
parser_c.add_argument(
|
|
345
|
+
"-f",
|
|
346
|
+
"--force",
|
|
347
|
+
required=False,
|
|
348
|
+
action="store_true",
|
|
349
|
+
help="Force clean",
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Push subcommand
|
|
353
|
+
parser_p = subparsers.add_parser("p", help="Push to remote")
|
|
354
|
+
parser_p.add_argument("--all", "-a", action="store_true", help="Push all branches")
|
|
355
|
+
|
|
356
|
+
# Pull subcommand
|
|
357
|
+
parser_pl = subparsers.add_parser("pl", help="Pull from remote")
|
|
358
|
+
parser_pl.add_argument(
|
|
359
|
+
"--rebase", "-r", action="store_true", help="Pull with rebase"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Checkout subcommand
|
|
363
|
+
parser_co = subparsers.add_parser("co", help="Checkout branch")
|
|
364
|
+
parser_co.add_argument("branch", help="Branch name")
|
|
365
|
+
parser_co.add_argument(
|
|
366
|
+
"-b", "--create", action="store_true", help="Create new branch"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Branch subcommand
|
|
370
|
+
parser_b = subparsers.add_parser("b", help="List or manage branches")
|
|
371
|
+
parser_b.add_argument("-a", "--all", action="store_true", help="List all branches")
|
|
372
|
+
parser_b.add_argument("-d", "--delete", metavar="BRANCH", help="Delete branch")
|
|
373
|
+
|
|
374
|
+
return parser.parse_args()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def main() -> None:
|
|
378
|
+
"""Main entry point for gittool CLI."""
|
|
379
|
+
args = parse_args()
|
|
380
|
+
command = _COMMAND_MAP.get(args.command)
|
|
381
|
+
|
|
382
|
+
if not command:
|
|
383
|
+
raise ValueError(f"Invalid command: {args.command}")
|
|
384
|
+
|
|
385
|
+
cmd_instance = command()
|
|
386
|
+
if args.command == "i" or args.command == "a":
|
|
387
|
+
cmd_instance.run()
|
|
388
|
+
elif args.command == "c":
|
|
389
|
+
cmd_instance.run(force=args.force)
|
|
390
|
+
elif args.command == "p":
|
|
391
|
+
cmd_instance.run(all_branches=args.all)
|
|
392
|
+
elif args.command == "pl":
|
|
393
|
+
cmd_instance.run(rebase=args.rebase)
|
|
394
|
+
elif args.command == "co":
|
|
395
|
+
cmd_instance.run(branch=args.branch, create=args.create)
|
|
396
|
+
elif args.command == "b":
|
|
397
|
+
cmd_instance.run(list_all=args.all, delete=args.delete)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
if __name__ == "__main__":
|
|
401
|
+
main()
|