pysfi 0.1.0__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.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: pysfi
3
+ Version: 0.1.0
4
+ Summary: Single File commands for Interactive python.
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+
8
+ # sfi
9
+
10
+ Single File commands for Interactive python.
11
+
12
+ ## Overview
13
+
14
+ sfi is a Python project that provides single-file command-line utilities, designed to be lightweight and easy-to-use.
15
+
16
+ ## Available Commands
17
+
18
+ ### [filedate](sfi/filedate/README.md)
19
+
20
+ A file date management tool that normalizes date prefixes in filenames.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ # Install using uv (recommended)
26
+ uv add sfi
27
+
28
+ # Or using pip
29
+ pip install sfi
30
+ ```
31
+
32
+ ## Development
33
+
34
+ ### Requirements
35
+
36
+ - Python >= 3.8
37
+ - [uv](https://github.com/astral-sh/uv) (recommended) or pip
38
+
39
+ ### Development Dependencies
40
+
41
+ ```bash
42
+ uv pip install -e ".[dev]"
43
+ ```
44
+
45
+ ### Code Standards
46
+
47
+ The project uses Ruff for code linting and formatting:
48
+
49
+ ```bash
50
+ # Check code
51
+ ruff check .
52
+
53
+ # Format code
54
+ ruff format .
55
+ ```
56
+
57
+ ## Project Structure
58
+
59
+ ```bash
60
+ sfi/
61
+ ├── pyproject.toml # Main project configuration
62
+ ├── README.md
63
+ └── sfi/
64
+ ├── __init__.py
65
+ └── filedate/ # filedate command module
66
+ ├── __init__.py
67
+ ├── filedate.py # Main implementation
68
+ ├── pyproject.toml
69
+ └── README.md # Detailed documentation
70
+ ```
71
+
72
+ ## License
73
+
74
+ MIT License
75
+
76
+ ## Contributing
77
+
78
+ Issues and Pull Requests are welcome!
@@ -0,0 +1,17 @@
1
+ sfi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sfi/alarmclock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ sfi/alarmclock/alarmclock.py,sha256=65G8OyTGpe4oQ2SFerQG1N9PVJ4KxO7WzgsTxpGm4O0,12509
4
+ sfi/embedinstall/embedinstall.py,sha256=N5EbTDdX4bE3W0qHGAwAUuepqFr0sbdZuPI3KWrtuUY,14936
5
+ sfi/filedate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ sfi/filedate/filedate.py,sha256=DpVp26lumE_Lz_4TgqUEX8IxtK3Y6yHSEFV8qJyegyk,3645
7
+ sfi/makepython/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ sfi/makepython/makepython.py,sha256=9-PPKlxAnaHm9NPhfpn1jcnEYix84rAM77tkHGQeWtA,2244
9
+ sfi/projectparse/projectparse.py,sha256=Ojg-z4lZEtjEBpJYWyznTgL307N45AxlQKnRkEH0P70,5525
10
+ sfi/pyloadergen/pyloadergen.py,sha256=yfMOeusAXm89gM02h21zQVoaSt6dkA2khXxfuU6Ognk,34182
11
+ sfi/pyloadergen/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ sfi/pyloadergen/tests/test_pyloadergen.py,sha256=rprkF2Flhg23S875ZWgaJYvwAHdHHh4K4mm5zetYx2I,12817
13
+ sfi/pypacker/fspacker.py,sha256=3tlS7qiWoH_kOzsp9eSWsQ-SY7-bSTugwfB-HIL69iE,3238
14
+ pysfi-0.1.0.dist-info/METADATA,sha256=S3mdC8SfZ4sl3pU9MneCAwu3Qf5c4r07x-bcN4ACJOw,1402
15
+ pysfi-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ pysfi-0.1.0.dist-info/entry_points.txt,sha256=aV9Do3TJC0HT-QMorLRmj-KYeCvRYqkdNgE5HeYj98w,320
17
+ pysfi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,8 @@
1
+ [console_scripts]
2
+ alarmclk = sfi.alarmclock.alarmclock:main
3
+ embedinstall = sfi.embedinstall.embedinstall:main
4
+ filedate = sfi.filedate.filedate:main
5
+ mkp = sfi.makepython.makepython:main
6
+ projectparse = sfi.projectparse.projectparse:main
7
+ pyloadergen = sfi.pyloadergen.pyloadergen:main
8
+ pypacker = sfi.pypacker.pypacker:main
sfi/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,367 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import logging
5
+ import random
6
+ import sys
7
+ from datetime import datetime, timedelta, timezone
8
+ from functools import partial
9
+ from typing import ClassVar
10
+
11
+ import qdarkstyle
12
+ from PySide2.QtCore import QSize, Qt, QTime, QTimer
13
+ from PySide2.QtGui import QCloseEvent
14
+ from PySide2.QtWidgets import (
15
+ QApplication,
16
+ QCheckBox,
17
+ QDialog,
18
+ QHBoxLayout,
19
+ QLabel,
20
+ QMainWindow,
21
+ QPushButton,
22
+ QTimeEdit,
23
+ QVBoxLayout,
24
+ QWidget,
25
+ )
26
+
27
+ __version__ = "0.1.2"
28
+ __build_date__ = "2025-09-16"
29
+
30
+
31
+ class AlarmClockConfig:
32
+ """Alarm clock configuration."""
33
+
34
+ ALARM_CLOCK_TITLE = "Digital Alarm Clock"
35
+
36
+ DIGITAL_FONT: str = "bold italic 81px 'Consolas'"
37
+ DIGITAL_COLOR: str = "#ccee00"
38
+ DIGITAL_BORDER_COLORS: ClassVar[list[str]] = [
39
+ "#00aa00",
40
+ "#eecc00",
41
+ "#aa00aa",
42
+ "#c0e0b0",
43
+ ]
44
+ DIGITAL_TIMER_FORMAT: str = "%H:%M:%S"
45
+ DIGITAL_UPDATE_INTERVAL: int = 1000
46
+
47
+ BLINK_TITLE: str = "Alarm Reminder!"
48
+ BLINK_CONTENT: str = "⏰ Time's Up!"
49
+ BLINK_TYPE: str = "color" # Options: 'color' or 'opacity'
50
+ BLINK_BG_COLORS: ClassVar[list[str]] = [
51
+ "#baf1ba",
52
+ "#f8ccc3",
53
+ "#aab4f0",
54
+ "#efaec0",
55
+ ]
56
+ BLINK_INTERVAL: ClassVar[int] = 300 # ms
57
+ DELAY_STEPS: ClassVar[list[int]] = [1, 5, 10, 15, 30, 60] # minutes
58
+
59
+
60
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
61
+
62
+ conf = AlarmClockConfig()
63
+ logger = logging.getLogger(__name__)
64
+
65
+
66
+ class DigitalClock(QLabel):
67
+ """Cool digital clock display."""
68
+
69
+ def __init__(self, parent: QWidget | None = None) -> None:
70
+ super().__init__(parent)
71
+
72
+ self.setAlignment(Qt.AlignCenter) # type: ignore
73
+
74
+ self._color = conf.DIGITAL_BORDER_COLORS[0]
75
+
76
+ # Timer to update current time
77
+ self._timer = QTimer()
78
+ self._timer.timeout.connect(self.update_time) # type: ignore
79
+ self._timer.start(conf.DIGITAL_UPDATE_INTERVAL) # Update every second
80
+
81
+ self.update_time()
82
+
83
+ def update_time(self) -> None:
84
+ """Update current time display."""
85
+ current = datetime.now(timezone.utc) + timedelta(hours=8) # Beijing time
86
+ self.setText(current.strftime(conf.DIGITAL_TIMER_FORMAT))
87
+ logger.debug(f"Updated time: {current}")
88
+
89
+ # Add blink effect
90
+ self._color = random.choice(
91
+ [_ for _ in conf.DIGITAL_BORDER_COLORS if _ != self._color],
92
+ )
93
+ self.setStyleSheet(f"""
94
+ font: {conf.DIGITAL_FONT};
95
+ color: {conf.DIGITAL_COLOR};
96
+ background-color: black;
97
+ border: 2px dashed {self._color};
98
+ border-radius: 10px;
99
+ padding: 10px;
100
+ """)
101
+
102
+
103
+ class BlinkDialog(QDialog):
104
+ """Alarm reminder dialog."""
105
+
106
+ def __init__(self) -> None:
107
+ super().__init__()
108
+
109
+ self.setWindowTitle(conf.BLINK_TITLE)
110
+ self.setModal(True)
111
+ self.setWindowFlags(
112
+ self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.WindowType.Dialog, # type: ignore
113
+ )
114
+ self.setFixedSize(QSize(400, 240))
115
+
116
+ layout = QVBoxLayout()
117
+ msg_label = QLabel(conf.BLINK_CONTENT)
118
+ msg_label.setStyleSheet("""
119
+ color: red;
120
+ font-size: 24px;
121
+ """)
122
+ msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # type: ignore
123
+
124
+ close_button = QPushButton("Close Alarm")
125
+ close_button.clicked.connect(self.accept) # type: ignore
126
+
127
+ layout.addWidget(msg_label)
128
+ layout.addWidget(close_button)
129
+ self.setLayout(layout)
130
+
131
+ # Prevent user from closing dialog by other means, ensure button click only
132
+ self.setWindowFlag(Qt.WindowCloseButtonHint, False) # type: ignore
133
+
134
+ # Blink control variables and timer
135
+ self.blink_timer = QTimer(self)
136
+ self.blink_timer.timeout.connect(self.update_blink) # type: ignore
137
+ self.blink_state = False
138
+ self.blink_type = conf.BLINK_TYPE
139
+
140
+ # Initialize style
141
+ self.bg_color = random.choice(conf.BLINK_BG_COLORS)
142
+ self.origin_style = self.styleSheet()
143
+ self.blink_timer.start(conf.BLINK_INTERVAL)
144
+
145
+ def update_blink(self) -> None:
146
+ """Timer timeout, update blink state."""
147
+ if self.blink_type == "color":
148
+ # Color blink logic
149
+ colors = [_ for _ in conf.BLINK_BG_COLORS[:] if _ != self.bg_color]
150
+ self.setStyleSheet(f"background-color: {random.choice(colors)}")
151
+ elif self.blink_type == "opacity":
152
+ # Opacity blink logic - Note: Some systems may not fully support window opacity
153
+ new_opacity = 0.3 if self.blink_state else 1.0
154
+ self.setWindowOpacity(new_opacity)
155
+
156
+ self.blink_state = not self.blink_state # Toggle state
157
+
158
+ def stop_blinking(self) -> None:
159
+ """Stop blinking, restore original style."""
160
+ self.blink_timer.stop()
161
+ self.setStyleSheet(self.origin_style) # Restore original style
162
+ self.setWindowOpacity(1.0) # Ensure opacity is restored
163
+
164
+ def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
165
+ """Override close event, ensure timer stops."""
166
+ self.stop_blinking()
167
+ super().closeEvent(event)
168
+
169
+
170
+ class AlarmClock(QMainWindow):
171
+ """Digital alarm clock GUI."""
172
+
173
+ def __init__(self) -> None:
174
+ super().__init__()
175
+ self.setWindowTitle(f"{conf.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
+
184
+ # Set window style
185
+ self.setStyleSheet("""
186
+ QMainWindow {
187
+ background-color: #2b2b2b;
188
+ }
189
+ QLabel {
190
+ color: #ffffff;
191
+ font-size: 14px;
192
+ }
193
+ QPushButton {
194
+ background-color: #3a3a3a;
195
+ color: white;
196
+ border: 1px solid #5a5a5a;
197
+ padding: 8px;
198
+ border-radius: 4px;
199
+ font-size: 14px;
200
+ }
201
+ QPushButton:hover {
202
+ background-color: #4a4a4a;
203
+ }
204
+ QPushButton:disabled {
205
+ background-color: #2a2a2a;
206
+ color: #6a6a6a;
207
+ }
208
+ QCheckBox {
209
+ color: white;
210
+ font-size: 14px;
211
+ }
212
+ QTimeEdit {
213
+ background-color: #3a3a3a;
214
+ color: white;
215
+ border: 1px solid #5a5a5a;
216
+ padding: 5px;
217
+ font-size: 14px;
218
+ }
219
+ """)
220
+
221
+ # Create central widget
222
+ central_widget = QWidget()
223
+ self.setCentralWidget(central_widget)
224
+
225
+ # Create layout
226
+ main_layout = QVBoxLayout()
227
+ main_layout.setSpacing(20)
228
+ central_widget.setLayout(main_layout)
229
+
230
+ # Cool digital clock display
231
+ self.digital_clock = DigitalClock(parent=self)
232
+ main_layout.addWidget(self.digital_clock)
233
+
234
+ # Alarm time setting
235
+ time_layout = QHBoxLayout()
236
+ time_label = QLabel("Alarm Time:")
237
+ time_label.setStyleSheet("color: white; font-size: 16px;")
238
+ self.alarm_time_edit = QTimeEdit()
239
+ self.alarm_time_edit.setDisplayFormat("HH:mm:ss")
240
+ self.alarm_time_edit.setTime(
241
+ QTime.currentTime().addSecs(conf.DELAY_STEPS[0] * 60),
242
+ )
243
+
244
+ time_layout.addWidget(time_label)
245
+ time_layout.addWidget(self.alarm_time_edit)
246
+ main_layout.addLayout(time_layout)
247
+
248
+ delay_layout = QHBoxLayout()
249
+ delay_label = QLabel("Delay (min):")
250
+ delay_label.setStyleSheet("color: white; font-size: 16px;")
251
+ delay_layout.addWidget(delay_label)
252
+ for minutes in conf.DELAY_STEPS:
253
+ button = QPushButton(str(minutes))
254
+ button.setStyleSheet("color: white; font-size: 16px;")
255
+ button.clicked.connect(partial(self.set_delay, minutes)) # type: ignore
256
+ delay_layout.addWidget(button)
257
+ main_layout.addLayout(delay_layout)
258
+
259
+ # Repeat option
260
+ self.repeat_checkbox = QCheckBox("Repeat")
261
+ main_layout.addWidget(self.repeat_checkbox)
262
+
263
+ # Control buttons
264
+ button_layout = QHBoxLayout()
265
+ self.set_alarm_button = QPushButton("Set Alarm")
266
+ self.set_alarm_button.clicked.connect(self.set_alarm) # type: ignore
267
+ self.cancel_alarm_button = QPushButton("Cancel Alarm")
268
+ self.cancel_alarm_button.clicked.connect(self.cancel_alarm) # type: ignore
269
+ self.cancel_alarm_button.setEnabled(False)
270
+ button_layout.addWidget(self.set_alarm_button)
271
+ button_layout.addWidget(self.cancel_alarm_button)
272
+ main_layout.addLayout(button_layout)
273
+
274
+ # Status display
275
+ self.status_label = QLabel("Alarm not set")
276
+ self.status_label.setAlignment(Qt.AlignCenter) # type: ignore
277
+ self.status_label.setStyleSheet("color: #aaaaaa; font-size: 16px;")
278
+ main_layout.addWidget(self.status_label)
279
+
280
+ # Alarm timer
281
+ self.alarm_timer = QTimer()
282
+ self.alarm_timer.timeout.connect(self.check_alarm) # type: ignore
283
+
284
+ # Alarm state
285
+ self.alarm_set = False
286
+ self.alarm_time: QTime = QTime() # Explicit type
287
+
288
+ def set_delay(self, minutes: int) -> None:
289
+ """Set delay alarm."""
290
+ self.alarm_time_edit.setTime(
291
+ QTime.currentTime().addSecs(minutes * 60),
292
+ )
293
+
294
+ def set_alarm(self) -> None:
295
+ """Set alarm."""
296
+ self.alarm_time = self.alarm_time_edit.time()
297
+ self.alarm_set = True
298
+ self.alarm_timer.start(1000) # Check every second
299
+ self.set_alarm_button.setEnabled(False)
300
+ self.cancel_alarm_button.setEnabled(True)
301
+ self.status_label.setText(
302
+ f"Alarm set: {self.alarm_time.toString('HH:mm:ss')}",
303
+ )
304
+ self.status_label.setStyleSheet(
305
+ "color: #00ff00; font-size: 16px; font-weight: bold;",
306
+ )
307
+
308
+ def cancel_alarm(self) -> None:
309
+ """Cancel alarm."""
310
+ self.alarm_set = False
311
+ self.alarm_timer.stop()
312
+ self.set_alarm_button.setEnabled(True)
313
+ self.cancel_alarm_button.setEnabled(False)
314
+ self.status_label.setText("Alarm cancelled")
315
+ self.status_label.setStyleSheet("color: #aaaaaa; font-size: 16px;")
316
+
317
+ def check_alarm(self) -> None:
318
+ """Check if alarm time has arrived."""
319
+ if not self.alarm_set:
320
+ return
321
+
322
+ current_time = QTime.currentTime()
323
+ if (
324
+ current_time.hour() == self.alarm_time.hour()
325
+ and current_time.minute() == self.alarm_time.minute()
326
+ and current_time.second() == self.alarm_time.second()
327
+ ):
328
+ # Show reminder message
329
+ dialog = BlinkDialog()
330
+ dialog.exec_()
331
+
332
+ 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
+ self.status_label.setStyleSheet("""
339
+ color: #ff0000;
340
+ font-size: 18px;
341
+ font-weight: bold;
342
+ background-color: #330000;
343
+ border-radius: 5px;
344
+ padding: 5px;
345
+ """)
346
+
347
+ # Cancel alarm if not repeating
348
+ if not self.repeat_checkbox.isChecked():
349
+ self.cancel_alarm()
350
+
351
+
352
+ 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
+ app = QApplication(sys.argv)
364
+ app.setStyleSheet(qdarkstyle.load_stylesheet_pyside2())
365
+ window = AlarmClock()
366
+ window.show()
367
+ sys.exit(app.exec_())