pysfi 0.1.7__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.
Files changed (55) hide show
  1. {pysfi-0.1.7.dist-info → pysfi-0.1.11.dist-info}/METADATA +11 -9
  2. pysfi-0.1.11.dist-info/RECORD +60 -0
  3. pysfi-0.1.11.dist-info/entry_points.txt +28 -0
  4. sfi/__init__.py +1 -1
  5. sfi/alarmclock/alarmclock.py +40 -40
  6. sfi/bumpversion/__init__.py +1 -1
  7. sfi/cleanbuild/cleanbuild.py +155 -0
  8. sfi/condasetup/condasetup.py +116 -0
  9. sfi/docscan/__init__.py +1 -1
  10. sfi/docscan/docscan.py +407 -103
  11. sfi/docscan/docscan_gui.py +1282 -596
  12. sfi/docscan/lang/eng.py +152 -0
  13. sfi/docscan/lang/zhcn.py +170 -0
  14. sfi/filedate/filedate.py +185 -112
  15. sfi/gittool/__init__.py +2 -0
  16. sfi/gittool/gittool.py +401 -0
  17. sfi/llmclient/llmclient.py +592 -0
  18. sfi/llmquantize/llmquantize.py +480 -0
  19. sfi/llmserver/llmserver.py +335 -0
  20. sfi/makepython/makepython.py +31 -30
  21. sfi/pdfsplit/pdfsplit.py +173 -173
  22. sfi/pyarchive/pyarchive.py +418 -0
  23. sfi/pyembedinstall/pyembedinstall.py +629 -0
  24. sfi/pylibpack/__init__.py +0 -0
  25. sfi/pylibpack/pylibpack.py +1457 -0
  26. sfi/pylibpack/rules/numpy.json +22 -0
  27. sfi/pylibpack/rules/pymupdf.json +10 -0
  28. sfi/pylibpack/rules/pyqt5.json +19 -0
  29. sfi/pylibpack/rules/pyside2.json +23 -0
  30. sfi/pylibpack/rules/scipy.json +23 -0
  31. sfi/pylibpack/rules/shiboken2.json +24 -0
  32. sfi/pyloadergen/pyloadergen.py +512 -227
  33. sfi/pypack/__init__.py +0 -0
  34. sfi/pypack/pypack.py +1142 -0
  35. sfi/pyprojectparse/__init__.py +0 -0
  36. sfi/pyprojectparse/pyprojectparse.py +500 -0
  37. sfi/pysourcepack/pysourcepack.py +308 -0
  38. sfi/quizbase/__init__.py +0 -0
  39. sfi/quizbase/quizbase.py +828 -0
  40. sfi/quizbase/quizbase_gui.py +987 -0
  41. sfi/regexvalidate/__init__.py +0 -0
  42. sfi/regexvalidate/regex_help.html +284 -0
  43. sfi/regexvalidate/regexvalidate.py +468 -0
  44. sfi/taskkill/taskkill.py +0 -2
  45. sfi/workflowengine/__init__.py +0 -0
  46. sfi/workflowengine/workflowengine.py +444 -0
  47. pysfi-0.1.7.dist-info/RECORD +0 -31
  48. pysfi-0.1.7.dist-info/entry_points.txt +0 -15
  49. sfi/embedinstall/embedinstall.py +0 -418
  50. sfi/projectparse/projectparse.py +0 -152
  51. sfi/pypacker/fspacker.py +0 -91
  52. {pysfi-0.1.7.dist-info → pysfi-0.1.11.dist-info}/WHEEL +0 -0
  53. /sfi/{embedinstall → docscan/lang}/__init__.py +0 -0
  54. /sfi/{projectparse → llmquantize}/__init__.py +0 -0
  55. /sfi/{pypacker → pyembedinstall}/__init__.py +0 -0
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()