pysfi 0.1.12__py3-none-any.whl → 0.1.14__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 (42) hide show
  1. {pysfi-0.1.12.dist-info → pysfi-0.1.14.dist-info}/METADATA +1 -1
  2. pysfi-0.1.14.dist-info/RECORD +68 -0
  3. {pysfi-0.1.12.dist-info → pysfi-0.1.14.dist-info}/entry_points.txt +3 -0
  4. sfi/__init__.py +19 -2
  5. sfi/alarmclock/__init__.py +3 -0
  6. sfi/alarmclock/alarmclock.py +23 -40
  7. sfi/bumpversion/__init__.py +3 -1
  8. sfi/bumpversion/bumpversion.py +64 -15
  9. sfi/cleanbuild/__init__.py +3 -0
  10. sfi/cleanbuild/cleanbuild.py +5 -1
  11. sfi/cli.py +25 -4
  12. sfi/condasetup/__init__.py +1 -0
  13. sfi/condasetup/condasetup.py +91 -76
  14. sfi/docdiff/__init__.py +1 -0
  15. sfi/docdiff/docdiff.py +3 -2
  16. sfi/docscan/__init__.py +1 -1
  17. sfi/docscan/docscan.py +78 -23
  18. sfi/docscan/docscan_gui.py +152 -48
  19. sfi/filedate/filedate.py +12 -5
  20. sfi/img2pdf/img2pdf.py +453 -0
  21. sfi/llmclient/llmclient.py +31 -8
  22. sfi/llmquantize/llmquantize.py +76 -37
  23. sfi/llmserver/__init__.py +1 -0
  24. sfi/llmserver/llmserver.py +63 -13
  25. sfi/makepython/makepython.py +1145 -201
  26. sfi/pdfsplit/pdfsplit.py +45 -12
  27. sfi/pyarchive/__init__.py +1 -0
  28. sfi/pyarchive/pyarchive.py +908 -278
  29. sfi/pyembedinstall/pyembedinstall.py +88 -89
  30. sfi/pylibpack/pylibpack.py +561 -463
  31. sfi/pyloadergen/pyloadergen.py +372 -218
  32. sfi/pypack/pypack.py +510 -959
  33. sfi/pyprojectparse/pyprojectparse.py +337 -40
  34. sfi/pysourcepack/__init__.py +1 -0
  35. sfi/pysourcepack/pysourcepack.py +210 -131
  36. sfi/quizbase/quizbase_gui.py +2 -2
  37. sfi/taskkill/taskkill.py +168 -59
  38. sfi/which/which.py +11 -3
  39. pysfi-0.1.12.dist-info/RECORD +0 -62
  40. sfi/workflowengine/workflowengine.py +0 -444
  41. {pysfi-0.1.12.dist-info → pysfi-0.1.14.dist-info}/WHEEL +0 -0
  42. /sfi/{workflowengine → img2pdf}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysfi
3
- Version: 0.1.12
3
+ Version: 0.1.14
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'
@@ -0,0 +1,68 @@
1
+ sfi/__init__.py,sha256=eGpIupcAG_5RqNCm908TwzDk-31Rhdn0iM-5Y2OwmIM,685
2
+ sfi/cli.py,sha256=o-sFO6I3X3DMO2I_Gzc9LziWBdm2ruHXpjbraXGzZi0,933
3
+ sfi/alarmclock/__init__.py,sha256=AUnUxITgFPYgSjn9ug2DEejrz5HDP56zZKYu-6JW1vY,72
4
+ sfi/alarmclock/alarmclock.py,sha256=ixVkbg548smUivRsqyI3YSZ81BWIrKawnuezAp3BzyE,11635
5
+ sfi/bumpversion/__init__.py,sha256=ExAxydqhwVCrA3YRid3JYult88HKsSTqYPt6996x0UU,122
6
+ sfi/bumpversion/bumpversion.py,sha256=Uxj0S8ZGmIRhfCx2ziWkVe5gHv74r68UPkiYzZcAwxo,21288
7
+ sfi/cleanbuild/__init__.py,sha256=_bXrBcQkHQTE2484-UMWAWz8kq-2Yjn9hleLcjF-pJg,98
8
+ sfi/cleanbuild/cleanbuild.py,sha256=Ilk4zBE48y6tUC3C5qp1pXdQ6CF0NZlra4ZbdiU5s2k,5270
9
+ sfi/condasetup/__init__.py,sha256=4W8VliAYUP1KY2gLJ_YDy2TmcXYVm-PY7XikQD_bFwA,2
10
+ sfi/condasetup/condasetup.py,sha256=tuRzRi4NKmIfqN4Gq5w-VmY7BnleizPhAbYta8AzH4c,4976
11
+ sfi/docdiff/__init__.py,sha256=4W8VliAYUP1KY2gLJ_YDy2TmcXYVm-PY7XikQD_bFwA,2
12
+ sfi/docdiff/docdiff.py,sha256=YR8e-gkbvjH0Bjt5fVVW0-8b7lbinQs6TRo4B5XgEjE,7783
13
+ sfi/docscan/__init__.py,sha256=eXv_iPeT_xOxv5lv4-axJQV28IISYYTqb6IRy3QTmIU,121
14
+ sfi/docscan/docscan.py,sha256=hXSOrfMikyg-Zsi_mpZvn31ea1J0un90ftwpRoXxNdA,42765
15
+ sfi/docscan/docscan_gui.py,sha256=KoXVHOQjUwtkxh5tI4V6mLBMl9PAP3Hp7zydJ_lppoQ,52345
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=eOR6LCwYosO6qyThPNeSCNImiU19UTQaqNWTkFKHAfs,6227
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=Rx8N9ZG2MrbzfiGAaZGEFlNGuOpP_gwNY5MmOSGTv3w,15198
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=crbZ4qnq6BAAkba_dBVYVhI9hhd9yBQ7XlNsWIrAes8,18888
29
+ sfi/llmserver/__init__.py,sha256=4W8VliAYUP1KY2gLJ_YDy2TmcXYVm-PY7XikQD_bFwA,2
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=GrzQTJyyB2PXugJK3yLxP1V5I_bSymW0qfkeqZvxRPA,43154
33
+ sfi/pdfsplit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ sfi/pdfsplit/pdfsplit.py,sha256=LOz7PuLlLBiXlFTjJej9G0UvJ03GZ9H-Gghy8ErvJzE,6576
35
+ sfi/pyarchive/__init__.py,sha256=4W8VliAYUP1KY2gLJ_YDy2TmcXYVm-PY7XikQD_bFwA,2
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=AJbMY4aHJq0YhyUXryBargNqCUTeSwaUuc3SiCcjcko,57685
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=vDT_lVkq0eHeFCCR3pwjZGnF0EyQZDesM6ze--Tl194,22894
51
+ sfi/pyprojectparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ sfi/pyprojectparse/pyprojectparse.py,sha256=qBHj8l-_ONxD4VA5KvCfXdvS3fbjfOls9gRX83I0TDE,30641
53
+ sfi/pysourcepack/__init__.py,sha256=4W8VliAYUP1KY2gLJ_YDy2TmcXYVm-PY7XikQD_bFwA,2
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
+ pysfi-0.1.14.dist-info/METADATA,sha256=1fFxs9DSKrmVtp4oKAD5HWOQa9qV9hvxKSDyLNPTaKI,4198
66
+ pysfi-0.1.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
67
+ pysfi-0.1.14.dist-info/entry_points.txt,sha256=iPXOCvVm9O0_qaqFnlFFlwklWsK7K4LygikeAhnkf2I,1259
68
+ pysfi-0.1.14.dist-info/RECORD,,
@@ -8,9 +8,11 @@ docscan = sfi.docscan.docscan:main
8
8
  docscan-gui = sfi.docscan.docscan_gui:main
9
9
  filedate = sfi.filedate.filedate:main
10
10
  gitt = sfi.gittool.gittool:main
11
+ img2pdf = sfi.img2pdf.img2pdf:main
11
12
  llmcli = sfi.llmclient.llmclient:main
12
13
  llmqnt = sfi.llmquantize.llmquantize:main
13
14
  llmsvr = sfi.llmserver.llmserver:main
15
+ makepython = sfi.makepython.makepython:main
14
16
  mkp = sfi.makepython.makepython:main
15
17
  pdfsplit = sfi.pdfsplit.pdfsplit:main
16
18
  pyarchive = sfi.pyarchive.pyarchive:main
@@ -19,6 +21,7 @@ pylibpack = sfi.pylibpack.pylibpack:main
19
21
  pyloadergen = sfi.pyloadergen.pyloadergen:main
20
22
  pyp = sfi.pypack.pypack:main
21
23
  pypack = sfi.pypack.pypack:main
24
+ pypp = sfi.pyprojectparse.pyprojectparse:main
22
25
  pyprojectparse = sfi.pyprojectparse.pyprojectparse:main
23
26
  pysourcepack = sfi.pysourcepack.pysourcepack:main
24
27
  quizbase = sfi.quizbase.quizbase:main
sfi/__init__.py CHANGED
@@ -1,3 +1,20 @@
1
- """Single File commands for Interactive python."""
1
+ """Single File commands for Interactive python.
2
2
 
3
- __version__ = "0.1.12"
3
+ pysfi (Python Single File Interactive) is a collection of Python utility
4
+ tools designed to streamline development workflows and enhance productivity.
5
+
6
+ This package provides:
7
+ - Document processing utilities (docscan, docdiff, pdfsplit, img2pdf)
8
+ - Development tools (makepython, bumpversion, cleanbuild)
9
+ - System management utilities (filedate, taskkill, gittool)
10
+ - AI/LLM tools (llmclient, llmserver, llmquantize)
11
+ - And many more specialized utilities
12
+
13
+ Each module can be used independently as a command-line tool
14
+ or imported as a Python library.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ __version__ = "0.1.14"
20
+ __all__ = ["__version__"]
@@ -0,0 +1,3 @@
1
+ """Alarm clock module for pysfi."""
2
+
3
+ from __future__ import annotations
@@ -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(frozen=True)
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
- [_ for _ in config.DIGITAL_BORDER_COLORS if _ != self._color],
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.accept)
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 = [_ for _ in config.BLINK_BG_COLORS[:] if _ != self.bg_color]
150
- self.setStyleSheet(f"background-color: {random.choice(colors)}")
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, restore original style."""
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_())
@@ -1,3 +1,5 @@
1
1
  """Bumpversion - Automated version number management tool."""
2
2
 
3
- __version__ = "0.1.12"
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.14"
@@ -11,6 +11,7 @@ import sys
11
11
  from dataclasses import dataclass
12
12
  from enum import Enum
13
13
  from pathlib import Path
14
+ from re import Pattern
14
15
  from typing import Final
15
16
 
16
17
  try:
@@ -21,7 +22,8 @@ except ImportError:
21
22
  logging.basicConfig(level=logging.INFO, format="%(message)s")
22
23
  logger = logging.getLogger(__name__)
23
24
 
24
- VERSION_PATTERN: Final = re.compile(
25
+ # Semantic versioning regex pattern
26
+ VERSION_PATTERN: Final[Pattern[str]] = re.compile(
25
27
  r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?"
26
28
  )
27
29
 
@@ -69,7 +71,12 @@ class Version:
69
71
  buildmetadata=match.group("buildmetadata"),
70
72
  )
71
73
 
72
- def bump(self, part: VersionPart, reset_prerelease: bool = True, prerelease: str | None = None) -> Version:
74
+ def bump(
75
+ self,
76
+ part: VersionPart,
77
+ reset_prerelease: bool = True,
78
+ prerelease: str | None = None,
79
+ ) -> Version:
73
80
  """Return a new version with specified part bumped."""
74
81
  # Determine the new prerelease value
75
82
  new_prerelease = None
@@ -121,7 +128,9 @@ class FileParser:
121
128
  """Parser for different file formats to extract and update version numbers."""
122
129
 
123
130
  @staticmethod
124
- def _write_with_original_line_ending(file_path: Path, content: str, original_content: str) -> None:
131
+ def _write_with_original_line_ending(
132
+ file_path: Path, content: str, original_content: str
133
+ ) -> None:
125
134
  """Write content to file while preserving original line ending style.
126
135
 
127
136
  Args:
@@ -167,7 +176,9 @@ class FileParser:
167
176
  new_content = re.sub(pattern, new_version_str, content)
168
177
 
169
178
  # Write back preserving original line endings
170
- FileParser._write_with_original_line_ending(file_path, new_content, original_bytes.decode("utf-8"))
179
+ FileParser._write_with_original_line_ending(
180
+ file_path, new_content, original_bytes.decode("utf-8")
181
+ )
171
182
 
172
183
  @staticmethod
173
184
  def parse_init_py(file_path: Path) -> tuple[Version, list[str]]:
@@ -285,7 +296,9 @@ class BumpversionManager:
285
296
  if projects_file.exists():
286
297
  with projects_file.open("r", encoding="utf-8") as f:
287
298
  projects_data = json.load(f)
288
- self.subproject_paths = {self.root_path / p for p in projects_data.get("subprojects", [])}
299
+ self.subproject_paths = {
300
+ self.root_path / p for p in projects_data.get("subprojects", [])
301
+ }
289
302
  except Exception:
290
303
  pass
291
304
 
@@ -313,10 +326,17 @@ class BumpversionManager:
313
326
  # Filter out unwanted directories
314
327
  excluded_dirs = {"__pycache__", ".venv", "venv", "env", ".git", "dist", "build"}
315
328
  init_files = [
316
- f for f in init_files if not any(part in excluded_dirs for part in f.parts) and "tests" not in f.parts
329
+ f
330
+ for f in init_files
331
+ if not any(part in excluded_dirs for part in f.parts)
332
+ and "tests" not in f.parts
317
333
  ]
318
334
  # Exclude files inside subprojects defined in projects.json
319
- init_files = [f for f in init_files if not any(str(f).startswith(str(sp)) for sp in self.subproject_paths)]
335
+ init_files = [
336
+ f
337
+ for f in init_files
338
+ if not any(str(f).startswith(str(sp)) for sp in self.subproject_paths)
339
+ ]
320
340
  detected_files.extend(init_files)
321
341
  for f in init_files:
322
342
  logger.info(f"Found: {f}")
@@ -442,7 +462,9 @@ class BumpversionManager:
442
462
  except (subprocess.CalledProcessError, FileNotFoundError):
443
463
  return False
444
464
 
445
- def _git_commit(self, version: Version, message: str | None, files: list[Path]) -> None:
465
+ def _git_commit(
466
+ self, version: Version, message: str | None, files: list[Path]
467
+ ) -> None:
446
468
  """Commit version changes to git."""
447
469
  commit_message = message or f"chore: bump version to {version}"
448
470
 
@@ -458,7 +480,9 @@ class BumpversionManager:
458
480
  tag_name = f"v{version}"
459
481
 
460
482
  try:
461
- subprocess.run(["git", "tag", "-a", tag_name, "-m", f"Version {version}"], check=True)
483
+ subprocess.run(
484
+ ["git", "tag", "-a", tag_name, "-m", f"Version {version}"], check=True
485
+ )
462
486
  logger.info(f"Git tag created: {tag_name}")
463
487
  except subprocess.CalledProcessError as e:
464
488
  logger.error(f"Git tag failed: {e}")
@@ -466,14 +490,39 @@ class BumpversionManager:
466
490
 
467
491
  def main() -> None:
468
492
  """Main entry point for bumpversion command."""
469
- parser = argparse.ArgumentParser(prog="bumpversion", description="Automated version number management tool")
470
- parser.add_argument("part", type=str, choices=["major", "minor", "patch"], help="Version part to bump")
471
- parser.add_argument("--files", "-f", type=str, nargs="*", help="Specific files to update (default: auto-detect)")
472
- parser.add_argument("--prerelease", "-p", type=str, help="Set prerelease tag (e.g., alpha, beta, rc1)")
473
- parser.add_argument("--commit", "-c", action="store_true", help="Commit changes to git")
493
+ parser = argparse.ArgumentParser(
494
+ prog="bumpversion", description="Automated version number management tool"
495
+ )
496
+ parser.add_argument(
497
+ "part",
498
+ type=str,
499
+ choices=["major", "minor", "patch"],
500
+ help="Version part to bump",
501
+ )
502
+ parser.add_argument(
503
+ "--files",
504
+ "-f",
505
+ type=str,
506
+ nargs="*",
507
+ help="Specific files to update (default: auto-detect)",
508
+ )
509
+ parser.add_argument(
510
+ "--prerelease",
511
+ "-p",
512
+ type=str,
513
+ help="Set prerelease tag (e.g., alpha, beta, rc1)",
514
+ )
515
+ parser.add_argument(
516
+ "--commit", "-c", action="store_true", help="Commit changes to git"
517
+ )
474
518
  parser.add_argument("--tag", "-t", action="store_true", help="Create git tag")
475
519
  parser.add_argument("--message", "-m", type=str, help="Custom commit message")
476
- parser.add_argument("--dry-run", "-n", action="store_true", help="Show what would be done without making changes")
520
+ parser.add_argument(
521
+ "--dry-run",
522
+ "-n",
523
+ action="store_true",
524
+ help="Show what would be done without making changes",
525
+ )
477
526
  parser.add_argument("--debug", "-d", action="store_true", help="Enable debug mode")
478
527
  args = parser.parse_args()
479
528
 
@@ -0,0 +1,3 @@
1
+ """Cleanbuild - Clean build artifacts and temporary files."""
2
+
3
+ from __future__ import annotations
@@ -13,6 +13,10 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
13
13
  from dataclasses import dataclass, field
14
14
  from functools import cached_property
15
15
  from pathlib import Path
16
+ from typing import Final
17
+
18
+ # Folders to clean
19
+ _BUILD_FOLDERS: Final[tuple[str, ...]] = ("build", "dist", ".venv", "node_modules")
16
20
 
17
21
 
18
22
  logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
@@ -20,7 +24,7 @@ logger = logging.getLogger(__name__)
20
24
 
21
25
  CWD = Path.cwd()
22
26
  PROJECT_ROOT = Path(__file__).parent.parent.parent
23
- _BUILD_FOLDERS = ("build", "dist", ".venv", "node_modules")
27
+
24
28
 
25
29
  @dataclass(frozen=True)
26
30
  class SearchResult:
sfi/cli.py CHANGED
@@ -1,11 +1,32 @@
1
+ from __future__ import annotations
2
+
1
3
  import argparse
2
4
 
3
- from sfi import __version__ as VERSION # noqa: N812
5
+ from sfi import __version__ as VERSION
6
+
7
+
8
+ def main() -> None:
9
+ """Main entry point for the pysfi CLI tool.
4
10
 
11
+ Parses command line arguments and handles version printing.
5
12
 
6
- def main():
7
- parser = argparse.ArgumentParser()
13
+ This is the central entry point for all pysfi subcommands.
14
+ Currently supports:
15
+ - Version information display
16
+
17
+ Future enhancements will include routing to specific
18
+ module commands based on subcommands.
19
+ """
20
+ parser = argparse.ArgumentParser(
21
+ prog="pysfi",
22
+ description="Python Single File Interactive - A collection of Python utility tools",
23
+ epilog="Use 'pysfi <command> --help' for more information on a specific command",
24
+ )
8
25
  parser.add_argument(
9
- "-v", "--version", action="version", version=f"%(prog)s v{VERSION}", help="Print version and exit"
26
+ "-v",
27
+ "--version",
28
+ action="version",
29
+ version=f"%(prog)s v{VERSION}",
30
+ help="Print version and exit",
10
31
  )
11
32
  parser.parse_args()
@@ -0,0 +1 @@
1
+