pysfi 0.1.11__py3-none-any.whl → 0.1.13__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.11.dist-info → pysfi-0.1.13.dist-info}/METADATA +3 -1
- pysfi-0.1.13.dist-info/RECORD +70 -0
- {pysfi-0.1.11.dist-info → pysfi-0.1.13.dist-info}/entry_points.txt +3 -0
- sfi/__init__.py +5 -3
- sfi/alarmclock/__init__.py +3 -0
- sfi/alarmclock/alarmclock.py +23 -40
- sfi/bumpversion/__init__.py +5 -3
- sfi/cleanbuild/__init__.py +3 -0
- sfi/cli.py +12 -2
- sfi/condasetup/__init__.py +1 -0
- sfi/docdiff/__init__.py +1 -0
- sfi/docdiff/docdiff.py +238 -0
- sfi/docscan/__init__.py +3 -3
- sfi/docscan/docscan_gui.py +150 -46
- sfi/img2pdf/__init__.py +0 -0
- sfi/img2pdf/img2pdf.py +453 -0
- sfi/llmclient/__init__.py +0 -0
- sfi/llmclient/llmclient.py +31 -8
- sfi/llmquantize/llmquantize.py +39 -11
- sfi/llmserver/__init__.py +1 -0
- sfi/llmserver/llmserver.py +63 -13
- sfi/makepython/makepython.py +507 -124
- sfi/pyarchive/__init__.py +1 -0
- sfi/pyarchive/pyarchive.py +908 -278
- sfi/pyembedinstall/pyembedinstall.py +88 -89
- sfi/pylibpack/pylibpack.py +571 -465
- sfi/pyloadergen/pyloadergen.py +372 -218
- sfi/pypack/pypack.py +494 -965
- sfi/pyprojectparse/pyprojectparse.py +328 -28
- sfi/pysourcepack/__init__.py +1 -0
- sfi/pysourcepack/pysourcepack.py +210 -131
- sfi/quizbase/quizbase_gui.py +2 -2
- sfi/taskkill/taskkill.py +168 -59
- sfi/which/which.py +11 -3
- sfi/workflowengine/workflowengine.py +225 -122
- pysfi-0.1.11.dist-info/RECORD +0 -60
- {pysfi-0.1.11.dist-info → pysfi-0.1.13.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysfi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
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'
|
|
@@ -17,6 +17,7 @@ Requires-Dist: pyside2>=5.15.2.1; extra == 'all'
|
|
|
17
17
|
Requires-Dist: pytesseract>=0.3.10; extra == 'all'
|
|
18
18
|
Requires-Dist: python-docx>=1.1.0; extra == 'all'
|
|
19
19
|
Requires-Dist: python-pptx>=0.6.21; extra == 'all'
|
|
20
|
+
Requires-Dist: pywin32>=311; (sys_platform == 'win32') and extra == 'all'
|
|
20
21
|
Provides-Extra: extra
|
|
21
22
|
Requires-Dist: ebooklib>=0.18; extra == 'extra'
|
|
22
23
|
Requires-Dist: markdown>=3.5; extra == 'extra'
|
|
@@ -33,6 +34,7 @@ Requires-Dist: openpyxl>=3.1.0; extra == 'office'
|
|
|
33
34
|
Requires-Dist: pymupdf>=1.24.11; extra == 'office'
|
|
34
35
|
Requires-Dist: python-docx>=1.1.0; extra == 'office'
|
|
35
36
|
Requires-Dist: python-pptx>=0.6.21; extra == 'office'
|
|
37
|
+
Requires-Dist: pywin32>=311; (sys_platform == 'win32') and extra == 'office'
|
|
36
38
|
Description-Content-Type: text/markdown
|
|
37
39
|
|
|
38
40
|
# pysfi
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
sfi/__init__.py,sha256=dZ1dacNjP1yRdG5N4-UtVhSZGUdEzRv4usjC1kr-3jY,116
|
|
2
|
+
sfi/cli.py,sha256=QF6-bdw8vOUMmFovjp4lYpWesHtae4oxHeEWDnt7_k8,504
|
|
3
|
+
sfi/alarmclock/__init__.py,sha256=yyFexrNdi65kz3l68YjT5Bdlq4FFqBR80H8vV7e_D58,75
|
|
4
|
+
sfi/alarmclock/alarmclock.py,sha256=ixVkbg548smUivRsqyI3YSZ81BWIrKawnuezAp3BzyE,11635
|
|
5
|
+
sfi/bumpversion/__init__.py,sha256=ajzMFse8CnLshD5qpKEr4fze3tnup2S9GWWyd7xpC2A,127
|
|
6
|
+
sfi/bumpversion/bumpversion.py,sha256=HOyHLaE0sZajrlcVZ8hsim8mPjz77qwQVSo6aIzjMXE,20735
|
|
7
|
+
sfi/cleanbuild/__init__.py,sha256=V4WV0xUvTaNGxawfYqlLT98t_8FeiA7ec1NW6R0-pGE,101
|
|
8
|
+
sfi/cleanbuild/cleanbuild.py,sha256=Fr6_cr3rj4llcEQ8yNTK-DHdSzmx1I4hYFJJHu5YEz0,5200
|
|
9
|
+
sfi/condasetup/__init__.py,sha256=a99mtb8qROZYvqLuhmlasVCgbmAL9nzVzOJFrVSWLGE,3
|
|
10
|
+
sfi/condasetup/condasetup.py,sha256=RlbXVYcAJYMau-ZzHOMzHrHl4r-lqNZO0bT-zWuzP_k,4581
|
|
11
|
+
sfi/docdiff/__init__.py,sha256=a99mtb8qROZYvqLuhmlasVCgbmAL9nzVzOJFrVSWLGE,3
|
|
12
|
+
sfi/docdiff/docdiff.py,sha256=anilgq16icu-UxdgRR7B_57G9CFJ79xBSjOm9DOQluY,7736
|
|
13
|
+
sfi/docscan/__init__.py,sha256=HQzSYiozGNtCdC771DXce57YwdXzQiSWFQKaTnnjAQU,124
|
|
14
|
+
sfi/docscan/docscan.py,sha256=rk8mjEI2SKNIliV-Yb41pfUmYBQ1tUhk5LHUNEjkszI,41890
|
|
15
|
+
sfi/docscan/docscan_gui.py,sha256=c7sJXvq5201Kh7iewalc50qoMnNR_eEna0NqoEJ7igw,52335
|
|
16
|
+
sfi/docscan/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
sfi/docscan/lang/eng.py,sha256=GcOcT9FLcPZRdJ-MbLRYyf6vDweZTQBu_zUnEFzRY84,8529
|
|
18
|
+
sfi/docscan/lang/zhcn.py,sha256=1SZwQjZF3oi9FsnzuZB-9v7P64sGm5oNmVjuL-rhcEQ,8885
|
|
19
|
+
sfi/filedate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
sfi/filedate/filedate.py,sha256=5FARcsB2Rlz2uTBxeYYjbIEJb9l1cyXj9WSoNKvSrRo,6068
|
|
21
|
+
sfi/gittool/__init__.py,sha256=Xqxw7UUX-TKkWOCB1QHq8AdIKTkU7x87Xr-E0yVmObA,24
|
|
22
|
+
sfi/gittool/gittool.py,sha256=BBE6gm9qP1fAWLqKprmsf7bOFgDvBvia8_bMaXc7dR4,11960
|
|
23
|
+
sfi/img2pdf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
sfi/img2pdf/img2pdf.py,sha256=rR6f5bMg-HoKMvSyu2rWfDbx0kmo6F6e1dg5z3710Wo,15129
|
|
25
|
+
sfi/llmclient/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
sfi/llmclient/llmclient.py,sha256=zvaT-HEiL3CM3uEpvzuseLPEqFQe6RO4_fhnA2djHo0,22681
|
|
27
|
+
sfi/llmquantize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
sfi/llmquantize/llmquantize.py,sha256=N4h0RwdpVZdZM3qDWbL34hLGpQ3XuPkP_F8kyhhM8ZI,18855
|
|
29
|
+
sfi/llmserver/__init__.py,sha256=a99mtb8qROZYvqLuhmlasVCgbmAL9nzVzOJFrVSWLGE,3
|
|
30
|
+
sfi/llmserver/llmserver.py,sha256=Fm4Go7wif4xMGomMFDsyJnYMafXsWemGkr-VfaeYa6w,13530
|
|
31
|
+
sfi/makepython/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
sfi/makepython/makepython.py,sha256=srinUE1Yr6zF-NjiUFVC1sUrpy8fYpDnCs7p648jWqc,23278
|
|
33
|
+
sfi/pdfsplit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
sfi/pdfsplit/pdfsplit.py,sha256=QWtW3GU28U2ZOyN5sCbH7jEMBpNbuAIzjXWOAXXW44M,6209
|
|
35
|
+
sfi/pyarchive/__init__.py,sha256=a99mtb8qROZYvqLuhmlasVCgbmAL9nzVzOJFrVSWLGE,3
|
|
36
|
+
sfi/pyarchive/pyarchive.py,sha256=OnPbIRA0C9JdeyNsVZ6rJg7ExItKyJz4jVw5W4c92DA,38293
|
|
37
|
+
sfi/pyembedinstall/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
sfi/pyembedinstall/pyembedinstall.py,sha256=LHnuvr63DXuntdS6a_7uQynOfarK-30WBUerSzawSHE,24171
|
|
39
|
+
sfi/pylibpack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
sfi/pylibpack/pylibpack.py,sha256=qK6J2_0VLMWQsKjcZ5njsvfqCnMRmGx8RweH2olhWTo,57818
|
|
41
|
+
sfi/pylibpack/rules/numpy.json,sha256=ee4gA5NBudFi3MaJA-QlBKQwiQAUb-eluF8HNVkl7Vk,384
|
|
42
|
+
sfi/pylibpack/rules/pymupdf.json,sha256=Hkzh8dvXKCzKx4aeHbu5E0qwgfbwQxZH2VLtQZzlMO4,153
|
|
43
|
+
sfi/pylibpack/rules/pyqt5.json,sha256=JKGnVSUMfXGR5XK1sbL1F6cAsEhl7hK12QkrulAB00M,374
|
|
44
|
+
sfi/pylibpack/rules/pyside2.json,sha256=uSSteT-3wDohWwQ36Z5mSOaSbxrR4565In4uZj_eR4w,557
|
|
45
|
+
sfi/pylibpack/rules/scipy.json,sha256=vTSi3W5BGWcwMkaDnyD6Yg7ijZdicPEUMw4fnRTnNf4,468
|
|
46
|
+
sfi/pylibpack/rules/shiboken2.json,sha256=9Pl3eslvergyjlyHNknkyN0oZlcH3049WULe5WjsmKM,515
|
|
47
|
+
sfi/pyloadergen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
sfi/pyloadergen/pyloadergen.py,sha256=VWJzc0opmMVthHE_RGbWeLe-DBaMn94gZo2yYLkf8cI,45696
|
|
49
|
+
sfi/pypack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
+
sfi/pypack/pypack.py,sha256=3LTCZMk8TqYhJbGv9AMLbe_Vi6bV7-cQUuHrWWO7QZU,22385
|
|
51
|
+
sfi/pyprojectparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
+
sfi/pyprojectparse/pyprojectparse.py,sha256=pHuwVCpVUlRoC9pRo3r6d0DfqqoEbsREsUuYjnkhqhU,30575
|
|
53
|
+
sfi/pysourcepack/__init__.py,sha256=a99mtb8qROZYvqLuhmlasVCgbmAL9nzVzOJFrVSWLGE,3
|
|
54
|
+
sfi/pysourcepack/pysourcepack.py,sha256=5_KrI2Y1TKKcoYfsFpTNXWguj6n8CKdca8lgoBCsL8k,12160
|
|
55
|
+
sfi/quizbase/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
|
+
sfi/quizbase/quizbase.py,sha256=3tPUuYexZ9TVsNPPO_Itmr5OvyHSgY5OSUZwPoQt9zg,30605
|
|
57
|
+
sfi/quizbase/quizbase_gui.py,sha256=m_Lj3au1a8gEv5x7KOTjomiP1NpXHUgHSPE4lLv63hY,34733
|
|
58
|
+
sfi/regexvalidate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
+
sfi/regexvalidate/regex_help.html,sha256=3ltx3nh-Y5kkbHy5D67KfWtLig3u5XEhIlPHdHLEuTE,12436
|
|
60
|
+
sfi/regexvalidate/regexvalidate.py,sha256=5C_M2EKt9Jlonq03v9zrqtsFfAKK3D1vF1kBxD6iUpE,18600
|
|
61
|
+
sfi/taskkill/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
|
+
sfi/taskkill/taskkill.py,sha256=wM9g8sWJVTy4GxXe26rKdax2lIBI-uH9wP5wRenriH4,11606
|
|
63
|
+
sfi/which/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
|
+
sfi/which/which.py,sha256=2YbGgSiT1ySapKVV1ESoPf4P-JU8vvzmsZY39NiVr6k,2596
|
|
65
|
+
sfi/workflowengine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
+
sfi/workflowengine/workflowengine.py,sha256=pPRsxWB2ZoDwcVTjsDlpiml-xZYiZBKjLcINu4TGBcE,19209
|
|
67
|
+
pysfi-0.1.13.dist-info/METADATA,sha256=6MAsufstxqnCpDvLj0O0xA6ZjTMvMAhwlccsrvu9uT4,4198
|
|
68
|
+
pysfi-0.1.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
69
|
+
pysfi-0.1.13.dist-info/entry_points.txt,sha256=ju_Bwp3L8-5Bpcj0PSCCDL8vGsGb4ZYpjfzprSdFrfA,1215
|
|
70
|
+
pysfi-0.1.13.dist-info/RECORD,,
|
|
@@ -3,10 +3,12 @@ alarmclk = sfi.alarmclock.alarmclock:main
|
|
|
3
3
|
bumpversion = sfi.bumpversion.bumpversion:main
|
|
4
4
|
cleanbuild = sfi.cleanbuild.cleanbuild:main
|
|
5
5
|
condasetup = sfi.condasetup.condasetup:main
|
|
6
|
+
docdiff = sfi.docdiff.docdiff:main
|
|
6
7
|
docscan = sfi.docscan.docscan:main
|
|
7
8
|
docscan-gui = sfi.docscan.docscan_gui:main
|
|
8
9
|
filedate = sfi.filedate.filedate:main
|
|
9
10
|
gitt = sfi.gittool.gittool:main
|
|
11
|
+
img2pdf = sfi.img2pdf.img2pdf:main
|
|
10
12
|
llmcli = sfi.llmclient.llmclient:main
|
|
11
13
|
llmqnt = sfi.llmquantize.llmquantize:main
|
|
12
14
|
llmsvr = sfi.llmserver.llmserver:main
|
|
@@ -18,6 +20,7 @@ pylibpack = sfi.pylibpack.pylibpack:main
|
|
|
18
20
|
pyloadergen = sfi.pyloadergen.pyloadergen:main
|
|
19
21
|
pyp = sfi.pypack.pypack:main
|
|
20
22
|
pypack = sfi.pypack.pypack:main
|
|
23
|
+
pypp = sfi.pyprojectparse.pyprojectparse:main
|
|
21
24
|
pyprojectparse = sfi.pyprojectparse.pyprojectparse:main
|
|
22
25
|
pysourcepack = sfi.pysourcepack.pysourcepack:main
|
|
23
26
|
quizbase = sfi.quizbase.quizbase:main
|
sfi/__init__.py
CHANGED
sfi/alarmclock/__init__.py
CHANGED
sfi/alarmclock/alarmclock.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import argparse
|
|
4
3
|
import logging
|
|
5
4
|
import random
|
|
6
5
|
import sys
|
|
@@ -8,7 +7,6 @@ from dataclasses import dataclass
|
|
|
8
7
|
from datetime import datetime, timedelta, timezone
|
|
9
8
|
from functools import partial
|
|
10
9
|
|
|
11
|
-
import qdarkstyle
|
|
12
10
|
from PySide2.QtCore import QSize, Qt, QTime, QTimer
|
|
13
11
|
from PySide2.QtGui import QCloseEvent
|
|
14
12
|
from PySide2.QtWidgets import (
|
|
@@ -28,7 +26,7 @@ __version__ = "0.1.3"
|
|
|
28
26
|
__build_date__ = "2026-01-22"
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
@dataclass
|
|
29
|
+
@dataclass
|
|
32
30
|
class AlarmClockConfig:
|
|
33
31
|
"""Configuration for the alarm clock application."""
|
|
34
32
|
|
|
@@ -87,9 +85,9 @@ class DigitalClock(QLabel):
|
|
|
87
85
|
logger.debug(f"Updated time: {current}")
|
|
88
86
|
|
|
89
87
|
# Add blink effect
|
|
90
|
-
self._color = random.choice(
|
|
91
|
-
|
|
92
|
-
)
|
|
88
|
+
self._color = random.choice([
|
|
89
|
+
c for c in config.DIGITAL_BORDER_COLORS if c != self._color
|
|
90
|
+
])
|
|
93
91
|
self.setStyleSheet(f"""
|
|
94
92
|
font: {config.DIGITAL_FONT};
|
|
95
93
|
color: {config.DIGITAL_COLOR};
|
|
@@ -112,6 +110,7 @@ class BlinkDialog(QDialog):
|
|
|
112
110
|
self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.WindowType.Dialog,
|
|
113
111
|
)
|
|
114
112
|
self.setFixedSize(QSize(400, 240))
|
|
113
|
+
self.setWindowFlag(Qt.WindowCloseButtonHint, False)
|
|
115
114
|
|
|
116
115
|
layout = QVBoxLayout()
|
|
117
116
|
msg_label = QLabel(config.BLINK_CONTENT)
|
|
@@ -122,32 +121,28 @@ class BlinkDialog(QDialog):
|
|
|
122
121
|
msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
123
122
|
|
|
124
123
|
close_button = QPushButton("Close Alarm")
|
|
125
|
-
close_button.clicked.connect(self.
|
|
124
|
+
close_button.clicked.connect(self.close_alarm)
|
|
126
125
|
|
|
127
126
|
layout.addWidget(msg_label)
|
|
128
127
|
layout.addWidget(close_button)
|
|
129
128
|
self.setLayout(layout)
|
|
130
129
|
|
|
131
|
-
# Prevent user from closing dialog by other means, ensure button click only
|
|
132
|
-
self.setWindowFlag(Qt.WindowCloseButtonHint, False)
|
|
133
|
-
|
|
134
130
|
# Blink control variables and timer
|
|
135
131
|
self.blink_timer = QTimer(self)
|
|
136
132
|
self.blink_timer.timeout.connect(self.update_blink)
|
|
137
133
|
self.blink_state = False
|
|
138
134
|
self.blink_type = config.BLINK_TYPE
|
|
139
|
-
|
|
140
|
-
# Initialize style
|
|
141
135
|
self.bg_color = random.choice(config.BLINK_BG_COLORS)
|
|
142
|
-
self.origin_style = self.styleSheet()
|
|
143
136
|
self.blink_timer.start(config.BLINK_INTERVAL)
|
|
144
137
|
|
|
145
138
|
def update_blink(self) -> None:
|
|
146
139
|
"""Timer timeout, update blink state."""
|
|
147
140
|
if self.blink_type == "color":
|
|
148
141
|
# Color blink logic
|
|
149
|
-
colors = [
|
|
150
|
-
|
|
142
|
+
colors = [c for c in config.BLINK_BG_COLORS if c != self.bg_color]
|
|
143
|
+
new_color = random.choice(colors)
|
|
144
|
+
self.setStyleSheet(f"background-color: {new_color}")
|
|
145
|
+
self.bg_color = new_color
|
|
151
146
|
elif self.blink_type == "opacity":
|
|
152
147
|
# Opacity blink logic - Note: Some systems may not fully support window opacity
|
|
153
148
|
new_opacity = 0.3 if self.blink_state else 1.0
|
|
@@ -155,10 +150,14 @@ class BlinkDialog(QDialog):
|
|
|
155
150
|
|
|
156
151
|
self.blink_state = not self.blink_state # Toggle state
|
|
157
152
|
|
|
153
|
+
def close_alarm(self) -> None:
|
|
154
|
+
"""Close alarm dialog and stop blinking."""
|
|
155
|
+
self.stop_blinking()
|
|
156
|
+
self.accept()
|
|
157
|
+
|
|
158
158
|
def stop_blinking(self) -> None:
|
|
159
|
-
"""Stop blinking
|
|
159
|
+
"""Stop blinking."""
|
|
160
160
|
self.blink_timer.stop()
|
|
161
|
-
self.setStyleSheet(self.origin_style) # Restore original style
|
|
162
161
|
self.setWindowOpacity(1.0) # Ensure opacity is restored
|
|
163
162
|
|
|
164
163
|
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
|
@@ -173,13 +172,6 @@ class AlarmClock(QMainWindow):
|
|
|
173
172
|
def __init__(self) -> None:
|
|
174
173
|
super().__init__()
|
|
175
174
|
self.setWindowTitle(f"{config.ALARM_CLOCK_TITLE} v{__version__}")
|
|
176
|
-
self.setGeometry(
|
|
177
|
-
QApplication.desktop().screenGeometry().center().x() - self.width() // 4,
|
|
178
|
-
QApplication.desktop().screenGeometry().center().y() - self.height() // 2,
|
|
179
|
-
self.width(),
|
|
180
|
-
self.height(),
|
|
181
|
-
)
|
|
182
|
-
self.adjustSize()
|
|
183
175
|
|
|
184
176
|
# Set window style
|
|
185
177
|
self.setStyleSheet("""
|
|
@@ -277,6 +269,13 @@ class AlarmClock(QMainWindow):
|
|
|
277
269
|
self.status_label.setStyleSheet("color: #aaaaaa; font-size: 16px;")
|
|
278
270
|
main_layout.addWidget(self.status_label)
|
|
279
271
|
|
|
272
|
+
# Center window on screen
|
|
273
|
+
self.adjustSize()
|
|
274
|
+
screen = QApplication.desktop().screenGeometry()
|
|
275
|
+
x = (screen.width() - self.width()) // 2
|
|
276
|
+
y = (screen.height() - self.height()) // 2
|
|
277
|
+
self.move(x, y)
|
|
278
|
+
|
|
280
279
|
# Alarm timer
|
|
281
280
|
self.alarm_timer = QTimer()
|
|
282
281
|
self.alarm_timer.timeout.connect(self.check_alarm)
|
|
@@ -330,11 +329,6 @@ class AlarmClock(QMainWindow):
|
|
|
330
329
|
dialog.exec_()
|
|
331
330
|
|
|
332
331
|
self.status_label.setText("⏰ Alarm Rang! ⏰")
|
|
333
|
-
self.status_label.setStyleSheet(
|
|
334
|
-
"color: #ff5555; font-size: 18px; font-weight: bold;",
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
# Add blink effect
|
|
338
332
|
self.status_label.setStyleSheet("""
|
|
339
333
|
color: #ff0000;
|
|
340
334
|
font-size: 18px;
|
|
@@ -350,18 +344,7 @@ class AlarmClock(QMainWindow):
|
|
|
350
344
|
|
|
351
345
|
|
|
352
346
|
def main() -> None:
|
|
353
|
-
parser = argparse.ArgumentParser(
|
|
354
|
-
prog="alarmclock", description="Digital Alarm Clock"
|
|
355
|
-
)
|
|
356
|
-
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode")
|
|
357
|
-
parser.add_argument("-v", "--version", action="version", version=f"{__version__}")
|
|
358
|
-
|
|
359
|
-
args = parser.parse_args()
|
|
360
|
-
if args.debug:
|
|
361
|
-
logger.setLevel(logging.DEBUG)
|
|
362
|
-
|
|
363
347
|
app = QApplication(sys.argv)
|
|
364
|
-
app.setStyleSheet(qdarkstyle.load_stylesheet_pyside2())
|
|
365
348
|
window = AlarmClock()
|
|
366
349
|
window.show()
|
|
367
350
|
sys.exit(app.exec_())
|
sfi/bumpversion/__init__.py
CHANGED
sfi/cli.py
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import argparse
|
|
2
4
|
|
|
3
5
|
from sfi import __version__ as VERSION # noqa: N812
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
def main():
|
|
8
|
+
def main() -> None:
|
|
9
|
+
"""Main entry point for the pysfi CLI tool.
|
|
10
|
+
|
|
11
|
+
Parses command line arguments and handles version printing.
|
|
12
|
+
"""
|
|
7
13
|
parser = argparse.ArgumentParser()
|
|
8
14
|
parser.add_argument(
|
|
9
|
-
"-v",
|
|
15
|
+
"-v",
|
|
16
|
+
"--version",
|
|
17
|
+
action="version",
|
|
18
|
+
version=f"%(prog)s v{VERSION}",
|
|
19
|
+
help="Print version and exit",
|
|
10
20
|
)
|
|
11
21
|
parser.parse_args()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
sfi/docdiff/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
sfi/docdiff/docdiff.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import atexit
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import platform
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from functools import cached_property
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
CONFIG_FILE = Path.home() / ".pysfi" / "docdiff.json"
|
|
16
|
+
|
|
17
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class DocDiffConfig:
|
|
23
|
+
"""Document comparison configuration."""
|
|
24
|
+
|
|
25
|
+
DOC_DIFF_TITLE: str = "Comparison Result"
|
|
26
|
+
OUTPUT_DIR: str = str(Path.home()) # Use current directory if empty
|
|
27
|
+
COMPARE_MODE: str = "original" # Options: original, revised
|
|
28
|
+
SHOW_CHANGES: bool = True
|
|
29
|
+
TRACK_REVISIONS: bool = True
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
if CONFIG_FILE.exists():
|
|
33
|
+
logger.info("Loading configuration from %s", CONFIG_FILE)
|
|
34
|
+
config_data = json.loads(CONFIG_FILE.read_text())
|
|
35
|
+
# Update configuration items, keeping defaults as fallback
|
|
36
|
+
for key, value in config_data.items():
|
|
37
|
+
if hasattr(self, key):
|
|
38
|
+
setattr(self, key, value)
|
|
39
|
+
else:
|
|
40
|
+
logger.info("Using default configuration")
|
|
41
|
+
|
|
42
|
+
def save(self) -> None:
|
|
43
|
+
"""Save configuration."""
|
|
44
|
+
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
CONFIG_FILE.write_text(json.dumps(vars(self), indent=4))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
conf = DocDiffConfig()
|
|
49
|
+
atexit.register(conf.save)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class DiffDocCommand:
|
|
54
|
+
"""Document comparison command."""
|
|
55
|
+
|
|
56
|
+
old_doc: Path
|
|
57
|
+
new_doc: Path
|
|
58
|
+
output_path: Path | None = None
|
|
59
|
+
|
|
60
|
+
def run(self) -> None:
|
|
61
|
+
"""Run the document comparison command."""
|
|
62
|
+
if platform.system() != "Windows":
|
|
63
|
+
logger.error("This tool is only available on Windows.")
|
|
64
|
+
return
|
|
65
|
+
if not self.old_doc.exists():
|
|
66
|
+
logger.error(f"Old file does not exist: {self.old_doc}")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if not self.new_doc.exists():
|
|
70
|
+
logger.error(f"New file does not exist: {self.new_doc}")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if not self.validate_files:
|
|
74
|
+
logger.error("Invalid file paths or extensions")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if self.word_app is None:
|
|
78
|
+
logger.error("Word application is not available")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if self.compare_data is None:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
self.output.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
try:
|
|
86
|
+
self.compare_data.SaveAs2(str(self.output))
|
|
87
|
+
self.compare_data.Close()
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.exception(f"Comparison failed: {e}")
|
|
90
|
+
else:
|
|
91
|
+
logger.info(f"Comparison completed. Saved to: {self.output}")
|
|
92
|
+
finally:
|
|
93
|
+
try:
|
|
94
|
+
self.word_app.Documents.Close(SaveChanges=False)
|
|
95
|
+
except Exception:
|
|
96
|
+
logger.exception("Close document failed!")
|
|
97
|
+
else:
|
|
98
|
+
self.word_app.Quit()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
subprocess.run(
|
|
102
|
+
["taskkill", "/f", "/t", "/im", "WINWORD.EXE"], check=False
|
|
103
|
+
)
|
|
104
|
+
except Exception:
|
|
105
|
+
logger.exception("Taskkill failed!")
|
|
106
|
+
else:
|
|
107
|
+
logger.info("Taskkill completed successfully")
|
|
108
|
+
|
|
109
|
+
@cached_property
|
|
110
|
+
def word_app(self) -> Any:
|
|
111
|
+
try:
|
|
112
|
+
import win32com.client as win32 # type: ignore
|
|
113
|
+
except ImportError:
|
|
114
|
+
logger.exception("win32com.client is not installed, exiting.")
|
|
115
|
+
raise
|
|
116
|
+
else:
|
|
117
|
+
logger.info("Started Word application")
|
|
118
|
+
app = win32.gencache.EnsureDispatch("Word.Application") # type: ignore
|
|
119
|
+
app.Visible = False
|
|
120
|
+
app.DisplayAlerts = False
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
app.Options.TrackRevisions = conf.TRACK_REVISIONS
|
|
124
|
+
except AttributeError:
|
|
125
|
+
logger.warning(
|
|
126
|
+
"TrackRevisions option not available in this Word version"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return app
|
|
130
|
+
|
|
131
|
+
@cached_property
|
|
132
|
+
def validate_files(self) -> bool:
|
|
133
|
+
return all([
|
|
134
|
+
self.old_doc.exists(),
|
|
135
|
+
self.new_doc.exists(),
|
|
136
|
+
self.old_doc.suffix.lower() in [".doc", ".docx"],
|
|
137
|
+
self.new_doc.suffix.lower() in [".doc", ".docx"],
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
@cached_property
|
|
141
|
+
def compare_data(self) -> Any:
|
|
142
|
+
try:
|
|
143
|
+
compared = self.word_app.CompareDocuments(
|
|
144
|
+
self.old_doc_data,
|
|
145
|
+
self.new_doc_data,
|
|
146
|
+
0,
|
|
147
|
+
2 if conf.COMPARE_MODE == "revised" else 0,
|
|
148
|
+
True,
|
|
149
|
+
)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.exception(f"Comparison failed: {e}")
|
|
152
|
+
return None
|
|
153
|
+
else:
|
|
154
|
+
if compared:
|
|
155
|
+
logger.info("Comparison completed successfully")
|
|
156
|
+
compared.ShowRevisions = conf.SHOW_CHANGES
|
|
157
|
+
return compared
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
@cached_property
|
|
161
|
+
def old_doc_data(self) -> Any:
|
|
162
|
+
logger.info(f"Opening old file: {self.old_doc}")
|
|
163
|
+
return self.word_app.Documents.Open(str(self.old_doc.resolve()))
|
|
164
|
+
|
|
165
|
+
@cached_property
|
|
166
|
+
def new_doc_data(self) -> Any:
|
|
167
|
+
logger.info(f"Opening new file: {self.new_doc}")
|
|
168
|
+
return self.word_app.Documents.Open(str(self.new_doc.resolve()))
|
|
169
|
+
|
|
170
|
+
@cached_property
|
|
171
|
+
def output(self) -> Path:
|
|
172
|
+
"""Determine the output directory for the comparison result."""
|
|
173
|
+
output_filename = (
|
|
174
|
+
f"{conf.DOC_DIFF_TITLE}@{time.strftime('%Y%m%d_%H_%M_%S')}.docx"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if self.output_path is None:
|
|
178
|
+
output_dir = (
|
|
179
|
+
Path(conf.OUTPUT_DIR) if conf.OUTPUT_DIR else self.new_doc.parent
|
|
180
|
+
)
|
|
181
|
+
return output_dir / output_filename
|
|
182
|
+
|
|
183
|
+
if self.output_path.is_dir():
|
|
184
|
+
return self.output_path / output_filename
|
|
185
|
+
elif self.output_path.is_file():
|
|
186
|
+
return self.output_path
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError(f"Invalid output path: {self.output_path}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def parse_args():
|
|
192
|
+
parser = argparse.ArgumentParser(description="Compare two doc/docx files.")
|
|
193
|
+
parser.add_argument(
|
|
194
|
+
"files", nargs=2, help="Two input files to compare (old_file new_file)"
|
|
195
|
+
)
|
|
196
|
+
parser.add_argument(
|
|
197
|
+
"-o", "--output", dest="output", default=".", help="Output file path"
|
|
198
|
+
)
|
|
199
|
+
parser.add_argument("--title", help="Title for the comparison result")
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
"--show-changes", action="store_true", help="Show changes in the comparison"
|
|
202
|
+
)
|
|
203
|
+
parser.add_argument(
|
|
204
|
+
"--hide-changes", action="store_true", help="Hide changes in the comparison"
|
|
205
|
+
)
|
|
206
|
+
parser.add_argument(
|
|
207
|
+
"--compare-mode",
|
|
208
|
+
choices=["original", "revised"],
|
|
209
|
+
help="Compare mode: original or revised",
|
|
210
|
+
)
|
|
211
|
+
parser.add_argument("--output-dir", help="Output directory for the result file")
|
|
212
|
+
|
|
213
|
+
args = parser.parse_args()
|
|
214
|
+
|
|
215
|
+
# Update configuration from command line arguments
|
|
216
|
+
if args.title:
|
|
217
|
+
conf.DOC_DIFF_TITLE = args.title
|
|
218
|
+
if args.show_changes:
|
|
219
|
+
conf.SHOW_CHANGES = True
|
|
220
|
+
if args.hide_changes:
|
|
221
|
+
conf.SHOW_CHANGES = False
|
|
222
|
+
if args.compare_mode:
|
|
223
|
+
conf.COMPARE_MODE = args.compare_mode
|
|
224
|
+
if args.output_dir:
|
|
225
|
+
conf.OUTPUT_DIR = args.output_dir
|
|
226
|
+
|
|
227
|
+
return args
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def main() -> None:
|
|
231
|
+
"""Compare two doc/docx files."""
|
|
232
|
+
args = parse_args()
|
|
233
|
+
|
|
234
|
+
DiffDocCommand(
|
|
235
|
+
Path(args.files[0]),
|
|
236
|
+
Path(args.files[1]),
|
|
237
|
+
Path(args.output),
|
|
238
|
+
).run()
|
sfi/docscan/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"""Document scanner module for scanning and extracting content from various document formats."""
|
|
2
|
-
|
|
3
|
-
__version__ = "0.1.
|
|
1
|
+
"""Document scanner module for scanning and extracting content from various document formats."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.13"
|