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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysfi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: Single File commands for Interactive python.
|
|
5
5
|
Requires-Python: >=3.8
|
|
6
6
|
Requires-Dist: tomli>=2.4.0; python_version < '3.11'
|
|
@@ -47,10 +47,10 @@ pysfi is a Python project that provides single-file command-line utilities, desi
|
|
|
47
47
|
|
|
48
48
|
- **alarmclk**: Alarm clock functionality
|
|
49
49
|
- **[bumpversion](sfi/bumpversion/README.md)**: Automated version number management tool
|
|
50
|
-
- **
|
|
50
|
+
- **pyembedinstall**: Embed installation utilities
|
|
51
51
|
- **[filedate](sfi/filedate/README.md)**: A file date management tool that normalizes date prefixes in filenames
|
|
52
52
|
- **mkp**: Make Python project utilities
|
|
53
|
-
- **
|
|
53
|
+
- **pyprojectparse**: Project parsing and analysis tools
|
|
54
54
|
- **pyloadergen**: Python loader code generation
|
|
55
55
|
- **pypack**: Python packaging utilities
|
|
56
56
|
|
|
@@ -101,8 +101,8 @@ pysfi/
|
|
|
101
101
|
│ ├── alarmclock.py
|
|
102
102
|
│ ├── pyproject.toml
|
|
103
103
|
│ └── __init__.py
|
|
104
|
-
├──
|
|
105
|
-
│ ├──
|
|
104
|
+
├── pyembedinstall/ # pyembedinstall command module
|
|
105
|
+
│ ├── pyembedinstall.py
|
|
106
106
|
│ ├── pyproject.toml
|
|
107
107
|
│ └── __init__.py
|
|
108
108
|
├── filedate/ # filedate command module
|
|
@@ -114,8 +114,8 @@ pysfi/
|
|
|
114
114
|
│ ├── makepython.py
|
|
115
115
|
│ ├── pyproject.toml
|
|
116
116
|
│ └── __init__.py
|
|
117
|
-
├──
|
|
118
|
-
│ ├──
|
|
117
|
+
├── pyprojectparse/ # pyprojectparse command module
|
|
118
|
+
│ ├── pyprojectparse.py
|
|
119
119
|
│ ├── pyproject.toml
|
|
120
120
|
│ └── __init__.py
|
|
121
121
|
├── pyloadergen/ # pyloadergen command module
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
sfi/__init__.py,sha256=W9IqwgCQr3sYmRyRC3ryCaJDJcMWBtyLzxp5CVsT3Nc,75
|
|
2
|
+
sfi/cli.py,sha256=bUUTOg18sJQbSKSfsVANhlMgSj9yzO2txIzFAd9B2Ok,296
|
|
3
|
+
sfi/alarmclock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
sfi/alarmclock/alarmclock.py,sha256=0HoacKlGdYq_hINAdl54Cz2E_z6nNjPyqif2xcEBQss,12381
|
|
5
|
+
sfi/bumpversion/__init__.py,sha256=j3XC03YiSDWRJV6UOcDWWsp09STfV5LrvzFkjsehSwA,86
|
|
6
|
+
sfi/bumpversion/bumpversion.py,sha256=HOyHLaE0sZajrlcVZ8hsim8mPjz77qwQVSo6aIzjMXE,20735
|
|
7
|
+
sfi/cleanbuild/cleanbuild.py,sha256=Fr6_cr3rj4llcEQ8yNTK-DHdSzmx1I4hYFJJHu5YEz0,5200
|
|
8
|
+
sfi/condasetup/condasetup.py,sha256=RlbXVYcAJYMau-ZzHOMzHrHl4r-lqNZO0bT-zWuzP_k,4581
|
|
9
|
+
sfi/docscan/__init__.py,sha256=qKkwfRoVBNMzNNdQk69QnFUrmJACtW9qbvoRloTDHfk,121
|
|
10
|
+
sfi/docscan/docscan.py,sha256=rk8mjEI2SKNIliV-Yb41pfUmYBQ1tUhk5LHUNEjkszI,41890
|
|
11
|
+
sfi/docscan/docscan_gui.py,sha256=T_blCyGGaWxL6rtjLIYW3nGdX8DpLQv73YbDnITR4eg,50671
|
|
12
|
+
sfi/docscan/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
sfi/docscan/lang/eng.py,sha256=GcOcT9FLcPZRdJ-MbLRYyf6vDweZTQBu_zUnEFzRY84,8529
|
|
14
|
+
sfi/docscan/lang/zhcn.py,sha256=1SZwQjZF3oi9FsnzuZB-9v7P64sGm5oNmVjuL-rhcEQ,8885
|
|
15
|
+
sfi/filedate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
sfi/filedate/filedate.py,sha256=5FARcsB2Rlz2uTBxeYYjbIEJb9l1cyXj9WSoNKvSrRo,6068
|
|
17
|
+
sfi/gittool/__init__.py,sha256=Xqxw7UUX-TKkWOCB1QHq8AdIKTkU7x87Xr-E0yVmObA,24
|
|
18
|
+
sfi/gittool/gittool.py,sha256=BBE6gm9qP1fAWLqKprmsf7bOFgDvBvia8_bMaXc7dR4,11960
|
|
19
|
+
sfi/llmclient/llmclient.py,sha256=SnFZ9c2cNvFeLeobJV1ls7Ewftaam4s-HVBYW2tgHPo,21706
|
|
20
|
+
sfi/llmquantize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
sfi/llmquantize/llmquantize.py,sha256=ILmfdJg7Rc7xAygfcVgkSKJ_qRAHDRZXjBymYFBy6fg,17693
|
|
22
|
+
sfi/llmserver/llmserver.py,sha256=vYEcNOV-OEvDULpzzCAJ0zHrXgqJFolOhqNdCZU0Bjs,11339
|
|
23
|
+
sfi/makepython/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
sfi/makepython/makepython.py,sha256=87lySTg0j1lZNIJMf8U_Go_fkuLkuwGDUf6LUbR_r6c,11445
|
|
25
|
+
sfi/pdfsplit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
sfi/pdfsplit/pdfsplit.py,sha256=QWtW3GU28U2ZOyN5sCbH7jEMBpNbuAIzjXWOAXXW44M,6209
|
|
27
|
+
sfi/pyarchive/pyarchive.py,sha256=1rkWY96U_DWbgTvdFGZto7dfutfhUo-OxmoVoaTo6WU,12892
|
|
28
|
+
sfi/pyembedinstall/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
sfi/pyembedinstall/pyembedinstall.py,sha256=kxp5YuwNHB29AVBMEJvzaIqTc7bEx-oBpKReGRJ1Pyw,23737
|
|
30
|
+
sfi/pylibpack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
sfi/pylibpack/pylibpack.py,sha256=lcGrzijibFFaJnxL8ZVALAGNHGs3g7-N4DO_AuiKwPs,54436
|
|
32
|
+
sfi/pylibpack/rules/numpy.json,sha256=ee4gA5NBudFi3MaJA-QlBKQwiQAUb-eluF8HNVkl7Vk,384
|
|
33
|
+
sfi/pylibpack/rules/pymupdf.json,sha256=Hkzh8dvXKCzKx4aeHbu5E0qwgfbwQxZH2VLtQZzlMO4,153
|
|
34
|
+
sfi/pylibpack/rules/pyqt5.json,sha256=JKGnVSUMfXGR5XK1sbL1F6cAsEhl7hK12QkrulAB00M,374
|
|
35
|
+
sfi/pylibpack/rules/pyside2.json,sha256=uSSteT-3wDohWwQ36Z5mSOaSbxrR4565In4uZj_eR4w,557
|
|
36
|
+
sfi/pylibpack/rules/scipy.json,sha256=vTSi3W5BGWcwMkaDnyD6Yg7ijZdicPEUMw4fnRTnNf4,468
|
|
37
|
+
sfi/pylibpack/rules/shiboken2.json,sha256=9Pl3eslvergyjlyHNknkyN0oZlcH3049WULe5WjsmKM,515
|
|
38
|
+
sfi/pyloadergen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
+
sfi/pyloadergen/pyloadergen.py,sha256=R2E6YBCUwfgT3SLr7paBBYEcuySLaj1q_CTZC2slwWQ,39741
|
|
40
|
+
sfi/pypack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
+
sfi/pypack/pypack.py,sha256=-l3jc4akSSlgEmwiB-kITP0ioBBy42-taHkEsGNEQNw,35168
|
|
42
|
+
sfi/pyprojectparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
+
sfi/pyprojectparse/pyprojectparse.py,sha256=MLDuQm5LFZW-TQ_GfaaeFSa18lsQq5u2ZTjnlkFQ_Ao,19112
|
|
44
|
+
sfi/pysourcepack/pysourcepack.py,sha256=qjBCFnY_3S7xwPgQ2GB0dr0WFbEj3uusZQ_udiU0Bok,9452
|
|
45
|
+
sfi/quizbase/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
sfi/quizbase/quizbase.py,sha256=3tPUuYexZ9TVsNPPO_Itmr5OvyHSgY5OSUZwPoQt9zg,30605
|
|
47
|
+
sfi/quizbase/quizbase_gui.py,sha256=7prc5tWkbUPzs1ofNc4xIC_aRrMCWB6RYN_NTq70p0Q,34729
|
|
48
|
+
sfi/regexvalidate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
sfi/regexvalidate/regex_help.html,sha256=3ltx3nh-Y5kkbHy5D67KfWtLig3u5XEhIlPHdHLEuTE,12436
|
|
50
|
+
sfi/regexvalidate/regexvalidate.py,sha256=5C_M2EKt9Jlonq03v9zrqtsFfAKK3D1vF1kBxD6iUpE,18600
|
|
51
|
+
sfi/taskkill/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
+
sfi/taskkill/taskkill.py,sha256=kRacCP78mDsZk44tfm4qblOplPxQuo3_6lHl6UQEmkU,7744
|
|
53
|
+
sfi/which/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
|
+
sfi/which/which.py,sha256=zVIAwZA-pGGngxkkwZ6IxDX3ozVHg7cLSYwYO9FjaIc,2439
|
|
55
|
+
sfi/workflowengine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
|
+
sfi/workflowengine/workflowengine.py,sha256=ck5PjyyjtWtbjN4ePEKsTWV6QR-BUlrfwrY6jih52jQ,17055
|
|
57
|
+
pysfi-0.1.11.dist-info/METADATA,sha256=4dkuiM92FRkYo-8ti_ZEmAxq1eoFa6HF0xs8ErJyc68,4047
|
|
58
|
+
pysfi-0.1.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
59
|
+
pysfi-0.1.11.dist-info/entry_points.txt,sha256=FhBsBY75x9e-AAyJ0t0HD1rb90FDllsM_IdVGHWZx9o,1099
|
|
60
|
+
pysfi-0.1.11.dist-info/RECORD,,
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
2
|
alarmclk = sfi.alarmclock.alarmclock:main
|
|
3
3
|
bumpversion = sfi.bumpversion.bumpversion:main
|
|
4
|
+
cleanbuild = sfi.cleanbuild.cleanbuild:main
|
|
5
|
+
condasetup = sfi.condasetup.condasetup:main
|
|
4
6
|
docscan = sfi.docscan.docscan:main
|
|
5
7
|
docscan-gui = sfi.docscan.docscan_gui:main
|
|
6
|
-
embedinstall = sfi.embedinstall.embedinstall:main
|
|
7
8
|
filedate = sfi.filedate.filedate:main
|
|
9
|
+
gitt = sfi.gittool.gittool:main
|
|
10
|
+
llmcli = sfi.llmclient.llmclient:main
|
|
11
|
+
llmqnt = sfi.llmquantize.llmquantize:main
|
|
12
|
+
llmsvr = sfi.llmserver.llmserver:main
|
|
8
13
|
mkp = sfi.makepython.makepython:main
|
|
9
14
|
pdfsplit = sfi.pdfsplit.pdfsplit:main
|
|
10
|
-
|
|
15
|
+
pyarchive = sfi.pyarchive.pyarchive:main
|
|
16
|
+
pyembedinstall = sfi.pyembedinstall.pyembedinstall:main
|
|
11
17
|
pylibpack = sfi.pylibpack.pylibpack:main
|
|
12
18
|
pyloadergen = sfi.pyloadergen.pyloadergen:main
|
|
13
19
|
pyp = sfi.pypack.pypack:main
|
|
14
20
|
pypack = sfi.pypack.pypack:main
|
|
21
|
+
pyprojectparse = sfi.pyprojectparse.pyprojectparse:main
|
|
15
22
|
pysourcepack = sfi.pysourcepack.pysourcepack:main
|
|
23
|
+
quizbase = sfi.quizbase.quizbase:main
|
|
24
|
+
quizbase-gui = sfi.quizbase.quizbase_gui:main
|
|
25
|
+
regval = sfi.regexvalidate.regexvalidate:main
|
|
16
26
|
sfi = sfi.cli:main
|
|
17
27
|
taskk = sfi.taskkill.taskkill:main
|
|
18
28
|
wch = sfi.which.which:main
|
sfi/__init__.py
CHANGED
sfi/alarmclock/alarmclock.py
CHANGED
|
@@ -4,9 +4,9 @@ import argparse
|
|
|
4
4
|
import logging
|
|
5
5
|
import random
|
|
6
6
|
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
7
8
|
from datetime import datetime, timedelta, timezone
|
|
8
9
|
from functools import partial
|
|
9
|
-
from typing import ClassVar
|
|
10
10
|
|
|
11
11
|
import qdarkstyle
|
|
12
12
|
from PySide2.QtCore import QSize, Qt, QTime, QTimer
|
|
@@ -24,42 +24,42 @@ from PySide2.QtWidgets import (
|
|
|
24
24
|
QWidget,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
__version__ = "0.1.
|
|
28
|
-
__build_date__ = "
|
|
27
|
+
__version__ = "0.1.3"
|
|
28
|
+
__build_date__ = "2026-01-22"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
@dataclass(frozen=True)
|
|
31
32
|
class AlarmClockConfig:
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
ALARM_CLOCK_TITLE = "Digital Alarm Clock"
|
|
33
|
+
"""Configuration for the alarm clock application."""
|
|
35
34
|
|
|
35
|
+
ALARM_CLOCK_TITLE: str = "Digital Alarm Clock"
|
|
36
36
|
DIGITAL_FONT: str = "bold italic 81px 'Consolas'"
|
|
37
37
|
DIGITAL_COLOR: str = "#ccee00"
|
|
38
|
-
DIGITAL_BORDER_COLORS:
|
|
38
|
+
DIGITAL_BORDER_COLORS: tuple[str, ...] = (
|
|
39
39
|
"#00aa00",
|
|
40
40
|
"#eecc00",
|
|
41
41
|
"#aa00aa",
|
|
42
42
|
"#c0e0b0",
|
|
43
|
-
|
|
43
|
+
)
|
|
44
44
|
DIGITAL_TIMER_FORMAT: str = "%H:%M:%S"
|
|
45
45
|
DIGITAL_UPDATE_INTERVAL: int = 1000
|
|
46
46
|
|
|
47
47
|
BLINK_TITLE: str = "Alarm Reminder!"
|
|
48
48
|
BLINK_CONTENT: str = "⏰ Time's Up!"
|
|
49
49
|
BLINK_TYPE: str = "color" # Options: 'color' or 'opacity'
|
|
50
|
-
BLINK_BG_COLORS:
|
|
50
|
+
BLINK_BG_COLORS: tuple[str, ...] = (
|
|
51
51
|
"#baf1ba",
|
|
52
52
|
"#f8ccc3",
|
|
53
53
|
"#aab4f0",
|
|
54
54
|
"#efaec0",
|
|
55
|
-
|
|
56
|
-
BLINK_INTERVAL:
|
|
57
|
-
DELAY_STEPS:
|
|
55
|
+
)
|
|
56
|
+
BLINK_INTERVAL: int = 300 # ms
|
|
57
|
+
DELAY_STEPS: tuple[int, ...] = (1, 5, 10, 15, 30, 60) # minutes
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
config = AlarmClockConfig()
|
|
63
63
|
logger = logging.getLogger(__name__)
|
|
64
64
|
|
|
65
65
|
|
|
@@ -69,30 +69,30 @@ class DigitalClock(QLabel):
|
|
|
69
69
|
def __init__(self, parent: QWidget | None = None) -> None:
|
|
70
70
|
super().__init__(parent)
|
|
71
71
|
|
|
72
|
-
self.setAlignment(Qt.AlignCenter)
|
|
72
|
+
self.setAlignment(Qt.AlignCenter)
|
|
73
73
|
|
|
74
|
-
self._color =
|
|
74
|
+
self._color = config.DIGITAL_BORDER_COLORS[0]
|
|
75
75
|
|
|
76
76
|
# Timer to update current time
|
|
77
77
|
self._timer = QTimer()
|
|
78
|
-
self._timer.timeout.connect(self.update_time)
|
|
79
|
-
self._timer.start(
|
|
78
|
+
self._timer.timeout.connect(self.update_time)
|
|
79
|
+
self._timer.start(config.DIGITAL_UPDATE_INTERVAL) # Update every second
|
|
80
80
|
|
|
81
81
|
self.update_time()
|
|
82
82
|
|
|
83
83
|
def update_time(self) -> None:
|
|
84
84
|
"""Update current time display."""
|
|
85
85
|
current = datetime.now(timezone.utc) + timedelta(hours=8) # Beijing time
|
|
86
|
-
self.setText(current.strftime(
|
|
86
|
+
self.setText(current.strftime(config.DIGITAL_TIMER_FORMAT))
|
|
87
87
|
logger.debug(f"Updated time: {current}")
|
|
88
88
|
|
|
89
89
|
# Add blink effect
|
|
90
90
|
self._color = random.choice(
|
|
91
|
-
[_ for _ in
|
|
91
|
+
[_ for _ in config.DIGITAL_BORDER_COLORS if _ != self._color],
|
|
92
92
|
)
|
|
93
93
|
self.setStyleSheet(f"""
|
|
94
|
-
font: {
|
|
95
|
-
color: {
|
|
94
|
+
font: {config.DIGITAL_FONT};
|
|
95
|
+
color: {config.DIGITAL_COLOR};
|
|
96
96
|
background-color: black;
|
|
97
97
|
border: 2px dashed {self._color};
|
|
98
98
|
border-radius: 10px;
|
|
@@ -106,47 +106,47 @@ class BlinkDialog(QDialog):
|
|
|
106
106
|
def __init__(self) -> None:
|
|
107
107
|
super().__init__()
|
|
108
108
|
|
|
109
|
-
self.setWindowTitle(
|
|
109
|
+
self.setWindowTitle(config.BLINK_TITLE)
|
|
110
110
|
self.setModal(True)
|
|
111
111
|
self.setWindowFlags(
|
|
112
|
-
self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.WindowType.Dialog,
|
|
112
|
+
self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.WindowType.Dialog,
|
|
113
113
|
)
|
|
114
114
|
self.setFixedSize(QSize(400, 240))
|
|
115
115
|
|
|
116
116
|
layout = QVBoxLayout()
|
|
117
|
-
msg_label = QLabel(
|
|
117
|
+
msg_label = QLabel(config.BLINK_CONTENT)
|
|
118
118
|
msg_label.setStyleSheet("""
|
|
119
119
|
color: red;
|
|
120
120
|
font-size: 24px;
|
|
121
121
|
""")
|
|
122
|
-
msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
122
|
+
msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
123
123
|
|
|
124
124
|
close_button = QPushButton("Close Alarm")
|
|
125
|
-
close_button.clicked.connect(self.accept)
|
|
125
|
+
close_button.clicked.connect(self.accept)
|
|
126
126
|
|
|
127
127
|
layout.addWidget(msg_label)
|
|
128
128
|
layout.addWidget(close_button)
|
|
129
129
|
self.setLayout(layout)
|
|
130
130
|
|
|
131
131
|
# Prevent user from closing dialog by other means, ensure button click only
|
|
132
|
-
self.setWindowFlag(Qt.WindowCloseButtonHint, False)
|
|
132
|
+
self.setWindowFlag(Qt.WindowCloseButtonHint, False)
|
|
133
133
|
|
|
134
134
|
# Blink control variables and timer
|
|
135
135
|
self.blink_timer = QTimer(self)
|
|
136
|
-
self.blink_timer.timeout.connect(self.update_blink)
|
|
136
|
+
self.blink_timer.timeout.connect(self.update_blink)
|
|
137
137
|
self.blink_state = False
|
|
138
|
-
self.blink_type =
|
|
138
|
+
self.blink_type = config.BLINK_TYPE
|
|
139
139
|
|
|
140
140
|
# Initialize style
|
|
141
|
-
self.bg_color = random.choice(
|
|
141
|
+
self.bg_color = random.choice(config.BLINK_BG_COLORS)
|
|
142
142
|
self.origin_style = self.styleSheet()
|
|
143
|
-
self.blink_timer.start(
|
|
143
|
+
self.blink_timer.start(config.BLINK_INTERVAL)
|
|
144
144
|
|
|
145
145
|
def update_blink(self) -> None:
|
|
146
146
|
"""Timer timeout, update blink state."""
|
|
147
147
|
if self.blink_type == "color":
|
|
148
148
|
# Color blink logic
|
|
149
|
-
colors = [_ for _ in
|
|
149
|
+
colors = [_ for _ in config.BLINK_BG_COLORS[:] if _ != self.bg_color]
|
|
150
150
|
self.setStyleSheet(f"background-color: {random.choice(colors)}")
|
|
151
151
|
elif self.blink_type == "opacity":
|
|
152
152
|
# Opacity blink logic - Note: Some systems may not fully support window opacity
|
|
@@ -172,7 +172,7 @@ class AlarmClock(QMainWindow):
|
|
|
172
172
|
|
|
173
173
|
def __init__(self) -> None:
|
|
174
174
|
super().__init__()
|
|
175
|
-
self.setWindowTitle(f"{
|
|
175
|
+
self.setWindowTitle(f"{config.ALARM_CLOCK_TITLE} v{__version__}")
|
|
176
176
|
self.setGeometry(
|
|
177
177
|
QApplication.desktop().screenGeometry().center().x() - self.width() // 4,
|
|
178
178
|
QApplication.desktop().screenGeometry().center().y() - self.height() // 2,
|
|
@@ -238,7 +238,7 @@ class AlarmClock(QMainWindow):
|
|
|
238
238
|
self.alarm_time_edit = QTimeEdit()
|
|
239
239
|
self.alarm_time_edit.setDisplayFormat("HH:mm:ss")
|
|
240
240
|
self.alarm_time_edit.setTime(
|
|
241
|
-
QTime.currentTime().addSecs(
|
|
241
|
+
QTime.currentTime().addSecs(config.DELAY_STEPS[0] * 60),
|
|
242
242
|
)
|
|
243
243
|
|
|
244
244
|
time_layout.addWidget(time_label)
|
|
@@ -249,10 +249,10 @@ class AlarmClock(QMainWindow):
|
|
|
249
249
|
delay_label = QLabel("Delay (min):")
|
|
250
250
|
delay_label.setStyleSheet("color: white; font-size: 16px;")
|
|
251
251
|
delay_layout.addWidget(delay_label)
|
|
252
|
-
for minutes in
|
|
252
|
+
for minutes in config.DELAY_STEPS:
|
|
253
253
|
button = QPushButton(str(minutes))
|
|
254
254
|
button.setStyleSheet("color: white; font-size: 16px;")
|
|
255
|
-
button.clicked.connect(partial(self.set_delay, minutes))
|
|
255
|
+
button.clicked.connect(partial(self.set_delay, minutes))
|
|
256
256
|
delay_layout.addWidget(button)
|
|
257
257
|
main_layout.addLayout(delay_layout)
|
|
258
258
|
|
|
@@ -263,9 +263,9 @@ class AlarmClock(QMainWindow):
|
|
|
263
263
|
# Control buttons
|
|
264
264
|
button_layout = QHBoxLayout()
|
|
265
265
|
self.set_alarm_button = QPushButton("Set Alarm")
|
|
266
|
-
self.set_alarm_button.clicked.connect(self.set_alarm)
|
|
266
|
+
self.set_alarm_button.clicked.connect(self.set_alarm)
|
|
267
267
|
self.cancel_alarm_button = QPushButton("Cancel Alarm")
|
|
268
|
-
self.cancel_alarm_button.clicked.connect(self.cancel_alarm)
|
|
268
|
+
self.cancel_alarm_button.clicked.connect(self.cancel_alarm)
|
|
269
269
|
self.cancel_alarm_button.setEnabled(False)
|
|
270
270
|
button_layout.addWidget(self.set_alarm_button)
|
|
271
271
|
button_layout.addWidget(self.cancel_alarm_button)
|
|
@@ -273,13 +273,13 @@ class AlarmClock(QMainWindow):
|
|
|
273
273
|
|
|
274
274
|
# Status display
|
|
275
275
|
self.status_label = QLabel("Alarm not set")
|
|
276
|
-
self.status_label.setAlignment(Qt.AlignCenter)
|
|
276
|
+
self.status_label.setAlignment(Qt.AlignCenter)
|
|
277
277
|
self.status_label.setStyleSheet("color: #aaaaaa; font-size: 16px;")
|
|
278
278
|
main_layout.addWidget(self.status_label)
|
|
279
279
|
|
|
280
280
|
# Alarm timer
|
|
281
281
|
self.alarm_timer = QTimer()
|
|
282
|
-
self.alarm_timer.timeout.connect(self.check_alarm)
|
|
282
|
+
self.alarm_timer.timeout.connect(self.check_alarm)
|
|
283
283
|
|
|
284
284
|
# Alarm state
|
|
285
285
|
self.alarm_set = False
|
sfi/bumpversion/__init__.py
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Clean build folders, such as .venv, node_modules, dist, build, etc."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import stat
|
|
10
|
+
import time
|
|
11
|
+
from collections import Counter
|
|
12
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from functools import cached_property
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
CWD = Path.cwd()
|
|
22
|
+
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
23
|
+
_BUILD_FOLDERS = ("build", "dist", ".venv", "node_modules")
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class SearchResult:
|
|
27
|
+
"""Search result for build folders."""
|
|
28
|
+
|
|
29
|
+
root_dir: Path = field(default_factory=Path)
|
|
30
|
+
folders_to_delete: list[Path] = field(default_factory=list)
|
|
31
|
+
folders_to_skip: list[Path] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
def __repr__(self) -> str:
|
|
34
|
+
return f"<SearchResult: root={self.root_dir}>, delete={self.delete_counter}, skip={self.skip_counter}\n>"
|
|
35
|
+
|
|
36
|
+
@cached_property
|
|
37
|
+
def delete_counter(self) -> Counter:
|
|
38
|
+
"""Get the most common folder names."""
|
|
39
|
+
return Counter([folder.name for folder in self.folders_to_delete])
|
|
40
|
+
|
|
41
|
+
@cached_property
|
|
42
|
+
def skip_counter(self) -> Counter:
|
|
43
|
+
"""Get the most common folder names."""
|
|
44
|
+
return Counter([folder.name for folder in self.folders_to_skip])
|
|
45
|
+
|
|
46
|
+
@cached_property
|
|
47
|
+
def delete_count(self) -> int:
|
|
48
|
+
"""Get the total number of folders to delete."""
|
|
49
|
+
return len(self.folders_to_delete)
|
|
50
|
+
|
|
51
|
+
def perform_delete(self):
|
|
52
|
+
"""Perform the delete operation."""
|
|
53
|
+
t0 = time.perf_counter()
|
|
54
|
+
deleted_count = 0
|
|
55
|
+
failed_folders = []
|
|
56
|
+
with ThreadPoolExecutor(max_workers=4) as executor:
|
|
57
|
+
future_to_folder = {
|
|
58
|
+
executor.submit(SearchResult.delete_folder, folder): folder
|
|
59
|
+
for folder in self.folders_to_delete
|
|
60
|
+
}
|
|
61
|
+
for future in as_completed(future_to_folder):
|
|
62
|
+
folder, error = future.result()
|
|
63
|
+
if error:
|
|
64
|
+
logger.error(f"Error deleting {folder}: {error}")
|
|
65
|
+
failed_folders.append(folder)
|
|
66
|
+
else:
|
|
67
|
+
logger.info(f"Deleted {folder}")
|
|
68
|
+
deleted_count += 1
|
|
69
|
+
|
|
70
|
+
if failed_folders:
|
|
71
|
+
logger.warning(f"Failed to delete {len(failed_folders)} folders")
|
|
72
|
+
logger.info(f"Successfully deleted {deleted_count}/{self.delete_count} folders")
|
|
73
|
+
logger.info(f"Time taken: {time.perf_counter() - t0:.4f}s")
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def handle_remove_readonly(func, path, _):
|
|
77
|
+
"""Handle Windows readonly files when removing."""
|
|
78
|
+
try:
|
|
79
|
+
os.chmod(path, stat.S_IWUSR)
|
|
80
|
+
func(path)
|
|
81
|
+
except OSError:
|
|
82
|
+
if not os.path.exists(path):
|
|
83
|
+
pass
|
|
84
|
+
else:
|
|
85
|
+
raise
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def delete_folder(folder: Path):
|
|
89
|
+
"""Delete a single folder with error handling."""
|
|
90
|
+
try:
|
|
91
|
+
shutil.rmtree(folder, onerror=SearchResult.handle_remove_readonly)
|
|
92
|
+
return folder, None
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return folder, e
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def is_target_folder(entry: Path) -> bool:
|
|
98
|
+
"""Check if a path is a build folder."""
|
|
99
|
+
return entry.is_dir() and entry.name in _BUILD_FOLDERS
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def from_path(directory: Path) -> SearchResult:
|
|
103
|
+
"""Create a SearchResult from a path."""
|
|
104
|
+
logger.info(f"Searching for build folders in {directory}")
|
|
105
|
+
|
|
106
|
+
folders_to_delete = []
|
|
107
|
+
folders_to_skip = []
|
|
108
|
+
for root, dirs, _ in os.walk(directory, onerror=lambda _: None):
|
|
109
|
+
root_path = Path(root)
|
|
110
|
+
for dir_name in dirs:
|
|
111
|
+
entry = root_path / dir_name
|
|
112
|
+
if entry == PROJECT_ROOT / ".venv":
|
|
113
|
+
folders_to_skip.append(entry)
|
|
114
|
+
continue
|
|
115
|
+
elif SearchResult.is_target_folder(entry):
|
|
116
|
+
folders_to_delete.append(entry)
|
|
117
|
+
|
|
118
|
+
result = SearchResult(
|
|
119
|
+
root_dir=directory,
|
|
120
|
+
folders_to_delete=folders_to_delete,
|
|
121
|
+
folders_to_skip=folders_to_skip,
|
|
122
|
+
)
|
|
123
|
+
logger.info(f"Found {result.delete_count} build folders to delete")
|
|
124
|
+
logger.info(f"Summary: {result}")
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def parse_args():
|
|
129
|
+
parser = argparse.ArgumentParser(description="Clean build folders")
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"directory", type=str, nargs="?", default=str(CWD), help="Directory to clean"
|
|
132
|
+
)
|
|
133
|
+
parser.add_argument("--yes", "-y", action="store_true", help="Skip confirmation")
|
|
134
|
+
return parser.parse_args()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main():
|
|
138
|
+
args = parse_args()
|
|
139
|
+
|
|
140
|
+
result = SearchResult.from_path(Path(args.directory))
|
|
141
|
+
if not args.yes:
|
|
142
|
+
logger.info(
|
|
143
|
+
f"\nAre you sure you want to clean build folders in {args.directory}? (y/n) [n]"
|
|
144
|
+
)
|
|
145
|
+
choice = input().lower()
|
|
146
|
+
if choice != "y":
|
|
147
|
+
logger.info("Dry run, no changes will be made")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
logger.info("Running clean build")
|
|
151
|
+
result.perform_delete()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
main()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
cwd = Path.cwd()
|
|
11
|
+
|
|
12
|
+
_CONDA_MIRROR_URLS: dict[str, frozenset[str]] = {
|
|
13
|
+
"tsinghua": frozenset(
|
|
14
|
+
[
|
|
15
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/",
|
|
16
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/",
|
|
17
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r/",
|
|
18
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2/",
|
|
19
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro/",
|
|
20
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/",
|
|
21
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/",
|
|
22
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/",
|
|
23
|
+
"https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/",
|
|
24
|
+
]
|
|
25
|
+
),
|
|
26
|
+
"ustc": frozenset(
|
|
27
|
+
[
|
|
28
|
+
"https://mirrors.ustc.edu.cn/anaconda/pkgs/main/",
|
|
29
|
+
"https://mirrors.ustc.edu.cn/anaconda/pkgs/free/",
|
|
30
|
+
"https://mirrors.ustc.edu.cn/anaconda/pkgs/r/",
|
|
31
|
+
"https://mirrors.ustc.edu.cn/anaconda/pkgs/msys2/",
|
|
32
|
+
"https://mirrors.ustc.edu.cn/anaconda/pkgs/pro/",
|
|
33
|
+
"https://mirrors.ustc.edu.cn/anaconda/pkgs/dev/",
|
|
34
|
+
"https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/",
|
|
35
|
+
"https://mirrors.ustc.edu.cn/anaconda/cloud/bioconda/",
|
|
36
|
+
"https://mirrors.ustc.edu.cn/anaconda/cloud/menpo/",
|
|
37
|
+
"https://mirrors.ustc.edu.cn/anaconda/cloud/pytorch/",
|
|
38
|
+
]
|
|
39
|
+
),
|
|
40
|
+
"bsfu": frozenset(
|
|
41
|
+
[
|
|
42
|
+
"https://mirrors.bsfu.edu.cn/anaconda/pkgs/main/",
|
|
43
|
+
"https://mirrors.bsfu.edu.cn/anaconda/pkgs/free/",
|
|
44
|
+
"https://mirrors.bsfu.edu.cn/anaconda/pkgs/r/",
|
|
45
|
+
"https://mirrors.bsfu.edu.cn/anaconda/pkgs/msys2/",
|
|
46
|
+
"https://mirrors.bsfu.edu.cn/anaconda/pkgs/pro/",
|
|
47
|
+
"https://mirrors.bsfu.edu.cn/anaconda/pkgs/dev/",
|
|
48
|
+
"https://mirrors.bsfu.edu.cn/anaconda/cloud/conda-forge/",
|
|
49
|
+
"https://mirrors.bsfu.edu.cn/anaconda/cloud/bioconda/",
|
|
50
|
+
"https://mirrors.bsfu.edu.cn/anaconda/cloud/menpo/",
|
|
51
|
+
"https://mirrors.bsfu.edu.cn/anaconda/cloud/pytorch/",
|
|
52
|
+
]
|
|
53
|
+
),
|
|
54
|
+
"aliyun": frozenset(
|
|
55
|
+
[
|
|
56
|
+
"https://mirrors.aliyun.com/anaconda/pkgs/main/",
|
|
57
|
+
"https://mirrors.aliyun.com/anaconda/pkgs/free/",
|
|
58
|
+
"https://mirrors.aliyun.com/anaconda/pkgs/r/",
|
|
59
|
+
"https://mirrors.aliyun.com/anaconda/pkgs/msys2/",
|
|
60
|
+
"https://mirrors.aliyun.com/anaconda/pkgs/pro/",
|
|
61
|
+
"https://mirrors.aliyun.com/anaconda/pkgs/dev/",
|
|
62
|
+
"https://mirrors.aliyun.com/anaconda/cloud/conda-forge/",
|
|
63
|
+
"https://mirrors.aliyun.com/anaconda/cloud/bioconda/",
|
|
64
|
+
"https://mirrors.aliyun.com/anaconda/cloud/menpo/",
|
|
65
|
+
"https://mirrors.aliyun.com/anaconda/cloud/pytorch/",
|
|
66
|
+
]
|
|
67
|
+
),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def set_conda_mirror(mirror: str = "tsinghua") -> None:
|
|
72
|
+
"""Set the Conda mirror for the given channel."""
|
|
73
|
+
if mirror in _CONDA_MIRROR_URLS:
|
|
74
|
+
old_config = Path.home() / ".condarc"
|
|
75
|
+
if old_config.exists():
|
|
76
|
+
logger.info("Found existing .condarc file, backing it up")
|
|
77
|
+
os.rename(old_config, Path.home() / ".condarc.bak")
|
|
78
|
+
else:
|
|
79
|
+
logger.debug("No existing .condarc file found")
|
|
80
|
+
|
|
81
|
+
mirror_urls = _CONDA_MIRROR_URLS[mirror]
|
|
82
|
+
for url in mirror_urls:
|
|
83
|
+
logger.debug(f"Adding mirror: {url}")
|
|
84
|
+
os.system(f"conda config --add channels {url}")
|
|
85
|
+
os.system("conda config --set show_channel_urls yes")
|
|
86
|
+
logger.info("Conda mirror set successfully")
|
|
87
|
+
else:
|
|
88
|
+
logger.error(f"Invalid mirror: {mirror}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def parse_args():
|
|
92
|
+
parser = argparse.ArgumentParser(description="Setup Conda environment for SFI")
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"mirror",
|
|
95
|
+
type=str,
|
|
96
|
+
choices=["tsinghua", "ustc", "bsfu", "aliyun"],
|
|
97
|
+
nargs="?",
|
|
98
|
+
default="tsinghua",
|
|
99
|
+
help="Conda mirror to use",
|
|
100
|
+
)
|
|
101
|
+
parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
|
|
102
|
+
|
|
103
|
+
return parser.parse_args()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
args = parse_args()
|
|
108
|
+
|
|
109
|
+
if args.debug:
|
|
110
|
+
logger.setLevel(logging.DEBUG)
|
|
111
|
+
|
|
112
|
+
set_conda_mirror(args.mirror)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
main()
|
sfi/docscan/__init__.py
CHANGED
sfi/docscan/docscan_gui.py
CHANGED