tm-timer 1.0.1__tar.gz → 2.0__tar.gz

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.
tm_timer-2.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ph4nt01
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
22
+
tm_timer-2.0/PKG-INFO ADDED
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: tm-timer
3
+ Version: 2.0
4
+ Summary: Minimalistic terminal-based timer with Study/Train tracking
5
+ Author: ph4nt01
6
+ Author-email: Ph4nt01 <ph4nt0.84@email.com>
7
+ License: MIT
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Dynamic: author
12
+ Dynamic: license-file
13
+ Dynamic: requires-python
14
+
15
+ # In The Name of God
16
+
17
+ ---
18
+
19
+ ## ⏱ tm : Minimalistic Terminal-based timer with Study/Train tracking
20
+
21
+ ### Features:
22
+
23
+ - Run from terminal:
24
+ - Countdown timer: `tm 10s`, `tm 25m`, `tm 1h30m`
25
+ - Study session: `tm --study` (tracks across sessions, resets daily)
26
+ - Train session: `tm --train` (tracks across sessions, resets daily)
27
+ - Daily study/train totals saved in `~/.tm_timer/data.json`
28
+ - Tables and statistics:
29
+ - `tm --studystatus` / `tm --trainstatus` → show daily table and Standard Deviation
30
+ - `tm --tables` → export `tables.md`
31
+ - Statistics:
32
+ - Average daily hours
33
+ - Standard Deviation of daily hours
34
+ - Status report (Very Good / Good / Not Good)
35
+ - Controls:
36
+ - Press `p` to pause/resume
37
+ - Press `q` to quit early
38
+ - Supports human durations like `2h45m30s`
39
+ - Simple, no graphical BLOAT.
40
+
41
+ ---
42
+
43
+ ### Installation:
44
+
45
+ ```bash
46
+ sudo apt install pipx # if not installed
47
+ pipx install tm-timer # to install tm-timer
48
+ pipx ensurepath # to add tm to PATH
49
+ exec $SHELL # reload shell
50
+
51
+ #or run locally with using cli.py
52
+ python3 cli.py 3h40m
53
+ ```
54
+
55
+ Example Usage:
56
+ bash
57
+ Copy code
58
+ tm --study
59
+ # Timer starts, press 'q' to stop, progress saved daily
60
+
61
+ tm --studystatus
62
+ # Shows study table and stats
63
+
64
+ tm --tables
65
+ # Exports study/train tables as tables.md
66
+ ⏱ tm preview:
67
+
tm_timer-2.0/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # In The Name of God
2
+
3
+ ---
4
+
5
+ ## ⏱ tm : Minimalistic Terminal-based timer with Study/Train tracking
6
+
7
+ ### Features:
8
+
9
+ - Run from terminal:
10
+ - Countdown timer: `tm 10s`, `tm 25m`, `tm 1h30m`
11
+ - Study session: `tm --study` (tracks across sessions, resets daily)
12
+ - Train session: `tm --train` (tracks across sessions, resets daily)
13
+ - Daily study/train totals saved in `~/.tm_timer/data.json`
14
+ - Tables and statistics:
15
+ - `tm --studystatus` / `tm --trainstatus` → show daily table and Standard Deviation
16
+ - `tm --tables` → export `tables.md`
17
+ - Statistics:
18
+ - Average daily hours
19
+ - Standard Deviation of daily hours
20
+ - Status report (Very Good / Good / Not Good)
21
+ - Controls:
22
+ - Press `p` to pause/resume
23
+ - Press `q` to quit early
24
+ - Supports human durations like `2h45m30s`
25
+ - Simple, no graphical BLOAT.
26
+
27
+ ---
28
+
29
+ ### Installation:
30
+
31
+ ```bash
32
+ sudo apt install pipx # if not installed
33
+ pipx install tm-timer # to install tm-timer
34
+ pipx ensurepath # to add tm to PATH
35
+ exec $SHELL # reload shell
36
+
37
+ #or run locally with using cli.py
38
+ python3 cli.py 3h40m
39
+ ```
40
+
41
+ Example Usage:
42
+ bash
43
+ Copy code
44
+ tm --study
45
+ # Timer starts, press 'q' to stop, progress saved daily
46
+
47
+ tm --studystatus
48
+ # Shows study table and stats
49
+
50
+ tm --tables
51
+ # Exports study/train tables as tables.md
52
+ ⏱ tm preview:
53
+
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tm-timer"
7
+ version = "2.0"
8
+ description = "Minimalistic terminal-based timer with Study/Train tracking"
9
+ readme = "README.md"
10
+ authors = [{ name="Ph4nt01", email="ph4nt0.84@email.com" }]
11
+ license = { text = "MIT" }
12
+ requires-python = ">=3.7"
13
+ dependencies = []
14
+
15
+ [project.scripts]
16
+ tm = "tm_timer.cli:main"
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="tm-timer",
5
- version="1.0.1",
5
+ version="2.0",
6
6
  packages=find_packages(),
7
7
  entry_points={
8
8
  "console_scripts": [
@@ -0,0 +1,387 @@
1
+ # File: cli.py
2
+ import argparse
3
+ import atexit
4
+ import datetime
5
+ import json
6
+ import math
7
+ import os
8
+ import shutil
9
+ import sys
10
+ import termios
11
+ import threading
12
+ import time
13
+ import tty
14
+ from pathlib import Path
15
+
16
+ # --- Terminal helpers (kept minimal) ---
17
+ try:
18
+ original_term_settings = termios.tcgetattr(sys.stdin.fileno())
19
+ except Exception:
20
+ original_term_settings = None
21
+
22
+
23
+ def restore_terminal():
24
+ if original_term_settings is None:
25
+ return
26
+ try:
27
+ termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, original_term_settings)
28
+ except Exception:
29
+ pass
30
+
31
+
32
+ atexit.register(restore_terminal)
33
+
34
+
35
+ def with_raw_mode():
36
+ fd = sys.stdin.fileno()
37
+ old_settings = termios.tcgetattr(fd)
38
+ tty.setraw(fd)
39
+ return old_settings
40
+
41
+
42
+ def get_char():
43
+ fd = sys.stdin.fileno()
44
+ old_settings = with_raw_mode()
45
+ try:
46
+ ch = sys.stdin.read(1)
47
+ finally:
48
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
49
+ return ch
50
+
51
+
52
+ # --- Storage setup ---
53
+ DATA_DIR = Path(os.path.expanduser("~")) / ".tm_timer"
54
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
55
+ DATA_FILE = DATA_DIR / "data.json"
56
+
57
+
58
+ def load_data():
59
+ if not DATA_FILE.exists():
60
+ return {"study": {}, "train": {}}
61
+ try:
62
+ with DATA_FILE.open("r", encoding="utf-8") as f:
63
+ data = json.load(f)
64
+ except Exception:
65
+ return {"study": {}, "train": {}}
66
+ # Ensure keys
67
+ if "study" not in data:
68
+ data["study"] = {}
69
+ if "train" not in data:
70
+ data["train"] = {}
71
+ return data
72
+
73
+
74
+ def save_data(data):
75
+ tmp = DATA_FILE.with_suffix(".tmp")
76
+ with tmp.open("w", encoding="utf-8") as f:
77
+ json.dump(data, f, indent=2)
78
+ tmp.replace(DATA_FILE)
79
+
80
+
81
+ def get_day_seconds(activity, date_str):
82
+ data = load_data()
83
+ return int(data.get(activity, {}).get(date_str, 0))
84
+
85
+
86
+ def update_day_seconds(activity, date_str, seconds):
87
+ data = load_data()
88
+ data.setdefault(activity, {})
89
+ data[activity][date_str] = int(seconds)
90
+ save_data(data)
91
+
92
+
93
+ # --- Utility formatters and stats ---
94
+ def format_hms(total_seconds):
95
+ total_seconds = int(round(total_seconds))
96
+ h = total_seconds // 3600
97
+ m = (total_seconds % 3600) // 60
98
+ s = total_seconds % 60
99
+ return f"{h:02d}:{m:02d}:{s:02d}"
100
+
101
+
102
+ def seconds_to_hours(seconds):
103
+ return seconds / 3600.0
104
+
105
+
106
+ def compute_stats_for_activity(activity):
107
+ data = load_data().get(activity, {})
108
+ # Use values sorted by date
109
+ seconds_list = [int(v) for k, v in sorted(data.items())]
110
+ n = len(seconds_list)
111
+ if n == 0:
112
+ return {"n": 0, "avg": 0.0, "sd": 0.0, "hours_list": []}
113
+ hours = [seconds_to_hours(s) for s in seconds_list]
114
+ avg = sum(hours) / n
115
+ if n < 2:
116
+ sd = 0.0
117
+ else:
118
+ # population SD (divide by n)
119
+ var = sum((x - avg) ** 2 for x in hours) / n
120
+ sd = math.sqrt(var)
121
+ return {"n": n, "avg": avg, "sd": sd, "hours_list": hours, "seconds_list": seconds_list}
122
+
123
+
124
+ def status_from_avg_sd(avg, sd):
125
+ half = avg / 2.0
126
+ if math.isclose(sd, half, rel_tol=1e-9, abs_tol=1e-9):
127
+ return "Good"
128
+ if sd > half:
129
+ return f"Not Good - should be lower than {half:.2f}h"
130
+ return "Very Good"
131
+
132
+
133
+ # --- Printing helpers ---
134
+ def print_activity_table(activity):
135
+ data = load_data().get(activity, {})
136
+ if not data:
137
+ print(f"No entries for {activity.upper()}.")
138
+ return
139
+ # Sort by date descending (most recent first)
140
+ items = sorted(data.items(), key=lambda kv: kv[0], reverse=True)
141
+ print(f"\n| Today's Name | {activity.upper()} Hour |")
142
+ print("| ------------ | ----------------- |")
143
+ for date_str, secs in items:
144
+ try:
145
+ dt = datetime.datetime.strptime(date_str, "%Y-%m-%d").date()
146
+ day_name = dt.strftime("%a").upper()
147
+ except Exception:
148
+ day_name = date_str
149
+ print(f"| {day_name} | {format_hms(secs)} |")
150
+ stats = compute_stats_for_activity(activity)
151
+ if stats["n"] < 2:
152
+ print("\nNot enough recording for statistics (need 2+ days).")
153
+ else:
154
+ print(
155
+ f"\n[AVG] = {stats['avg']:.2f}h [Standard Deviation of Time] = {stats['sd']:.2f}h Status = {status_from_avg_sd(stats['avg'], stats['sd'])}"
156
+ )
157
+
158
+
159
+ def write_tables_md(path="tables.md"):
160
+ data = load_data()
161
+ lines = []
162
+ for activity in ("study", "train"):
163
+ lines.append(f"## {activity.upper()} Table\n")
164
+ lines.append("| Date | Day | Time |\n")
165
+ lines.append("| ---- | --- | ---- |\n")
166
+ items = sorted(data.get(activity, {}).items(), key=lambda kv: kv[0], reverse=True)
167
+ for date_str, secs in items:
168
+ try:
169
+ dt = datetime.datetime.strptime(date_str, "%Y-%m-%d").date()
170
+ day_name = dt.strftime("%a").upper()
171
+ except Exception:
172
+ day_name = ""
173
+ lines.append(f"| {date_str} | {day_name} | {format_hms(secs)} |\n")
174
+ stats = compute_stats_for_activity(activity)
175
+ if stats["n"] < 2:
176
+ lines.append("\nNot enough recording for statistics (need 2+ days).\n\n")
177
+ else:
178
+ lines.append(
179
+ f"\n[AVG] = {stats['avg']:.2f}h [Standard Deviation of Time] = {stats['sd']:.2f}h Status = {status_from_avg_sd(stats['avg'], stats['sd'])}\n\n"
180
+ )
181
+ out_path = Path.cwd() / path
182
+ out_path.write_text("".join(lines), encoding="utf-8")
183
+ return str(out_path.resolve())
184
+
185
+
186
+ # --- Time parsing for countdown mode (compat) ---
187
+ import re
188
+
189
+
190
+ def parse_time_input(user_input):
191
+ pattern = r"^\s*(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?\s*$"
192
+ match = re.fullmatch(pattern, user_input.strip().lower())
193
+ if not match or not any(match.groups()):
194
+ raise ValueError("Invalid time format. Use format like '1h30m45s' or '25m' or '45s'.")
195
+ hours = int(match.group(1)) if match.group(1) else 0
196
+ minutes = int(match.group(2)) if match.group(2) else 0
197
+ seconds = int(match.group(3)) if match.group(3) else 0
198
+ return hours * 3600 + minutes * 60 + seconds
199
+
200
+
201
+ # --- Main run logic for activity timers (study/train) ---
202
+ def run_activity(activity):
203
+ global running, paused
204
+ running = True
205
+ paused = False
206
+
207
+ today = datetime.date.today()
208
+ today_str = today.isoformat()
209
+ stored_today = get_day_seconds(activity, today_str)
210
+
211
+ # seconds_float holds the current day's accumulated seconds (float for delta accumulation)
212
+ seconds_float = float(stored_today)
213
+
214
+ # Show initial stats (before starting)
215
+ stats_before = compute_stats_for_activity(activity)
216
+ if stats_before["n"] >= 2:
217
+ print(
218
+ f"[Before] [AVG] = {stats_before['avg']:.2f}h [Standard Deviation of Time] = {stats_before['sd']:.2f}h Status = {status_from_avg_sd(stats_before['avg'], stats_before['sd'])}"
219
+ )
220
+ else:
221
+ print("[Before] Not enough recordings to compute AVG/SD (need 2+ days).")
222
+
223
+ # input listener thread
224
+ def input_listener():
225
+ global running, paused
226
+ while running:
227
+ key = get_char()
228
+ if key.lower() == "p":
229
+ paused = not paused
230
+ elif key.lower() == "q":
231
+ running = False
232
+ break
233
+
234
+ input_thread = threading.Thread(target=input_listener, daemon=True)
235
+ input_thread.start()
236
+
237
+ last_ts = time.time()
238
+ current_day = today
239
+
240
+ try:
241
+ cols = shutil.get_terminal_size((80, 20)).columns
242
+ except Exception:
243
+ cols = 80
244
+
245
+ while running:
246
+ now_ts = time.time()
247
+ prev_ts = last_ts
248
+ last_ts = now_ts
249
+ if not paused:
250
+ prev_date = current_day
251
+ now_date = datetime.date.fromtimestamp(now_ts)
252
+ if now_date == prev_date:
253
+ seconds_float += now_ts - prev_ts
254
+ else:
255
+ # crossed midnight. compute split: seconds before midnight go to prev_date, remainder to now_date
256
+ midnight_ts = datetime.datetime.combine(now_date, datetime.time.min).timestamp()
257
+ # seconds that belong to previous day
258
+ seconds_to_prev = max(0.0, midnight_ts - prev_ts)
259
+ seconds_float += seconds_to_prev
260
+ # persist previous day
261
+ update_day_seconds(activity, prev_date.isoformat(), int(round(seconds_float)))
262
+ # For new day, start from previously stored seconds for that new day (if any)
263
+ new_day_str = now_date.isoformat()
264
+ stored_new_day = get_day_seconds(activity, new_day_str)
265
+ # remaining seconds from this delta that belong to new day:
266
+ seconds_for_new_day = max(0.0, now_ts - midnight_ts)
267
+ seconds_float = float(stored_new_day) + seconds_for_new_day
268
+ current_day = now_date
269
+
270
+ # display
271
+ disp_secs = int(round(seconds_float))
272
+ hrs, rem = divmod(disp_secs, 3600)
273
+ mins, secs = divmod(rem, 60)
274
+ msg = f"Timer: {hrs:02d}:{mins:02d}:{secs:02d} [Press 'p' to Pause/Resume, 'q' to Quit]"
275
+
276
+ if len(msg) >= cols:
277
+ msg = msg[: cols - 1]
278
+ sys.stdout.write(f"\r\033[K{msg}")
279
+ sys.stdout.flush()
280
+ time.sleep(0.2)
281
+
282
+ # when stopped, persist current day's seconds
283
+ try:
284
+ update_day_seconds(activity, current_day.isoformat(), int(round(seconds_float)))
285
+ except Exception as e:
286
+ print("\nError saving data:", e)
287
+
288
+ # final stats after save
289
+ stats_after = compute_stats_for_activity(activity)
290
+ print("\n\nTimer stopped.")
291
+ if stats_after["n"] < 2:
292
+ print("Not enough recordings to compute AVG/SD (need 2+ days).")
293
+ else:
294
+ print(
295
+ f"[AVG] = {stats_after['avg']:.2f}h [Standard Deviation of Time] = {stats_after['sd']:.2f}h Status = {status_from_avg_sd(stats_after['avg'], stats_after['sd'])}"
296
+ )
297
+
298
+
299
+ # --- Countdown/time-limited run (backwards compatible) ---
300
+ def run_countdown(target_seconds, target_input):
301
+ global running, paused
302
+ running = True
303
+ paused = False
304
+ seconds = 0
305
+
306
+ def input_listener():
307
+ global running, paused
308
+ while running:
309
+ key = get_char()
310
+ if key.lower() == "p":
311
+ paused = not paused
312
+ elif key.lower() == "q":
313
+ running = False
314
+ break
315
+
316
+ input_thread = threading.Thread(target=input_listener, daemon=True)
317
+ input_thread.start()
318
+
319
+ try:
320
+ cols = shutil.get_terminal_size((80, 20)).columns
321
+ except Exception:
322
+ cols = 80
323
+
324
+ while running and seconds < target_seconds:
325
+ if not paused:
326
+ mins, secs = divmod(seconds, 60)
327
+ hours, mins = divmod(mins, 60)
328
+ msg = f"Timer: {hours:02d}:{mins:02d}:{secs:02d} [Press 'p' to Pause/Resume, 'q' to Quit]"
329
+ msg = msg[: cols - 1] if len(msg) >= cols else msg
330
+ sys.stdout.write(f"\r\033[K{msg}")
331
+ sys.stdout.flush()
332
+ seconds += 1
333
+ time.sleep(1)
334
+ if seconds >= target_seconds:
335
+ sys.stdout.write("\n\n⏰ Time's up! Target of {} reached.\n".format(target_input))
336
+ restore_terminal()
337
+ print("Timer stopped.")
338
+
339
+
340
+ # --- CLI entrypoint ---
341
+ def main():
342
+ parser = argparse.ArgumentParser(description="tm_timer - terminal timer with study/train logging")
343
+ parser.add_argument("--study", action="store_true", help="Run study timer (no limit until 'q')")
344
+ parser.add_argument("--train", action="store_true", help="Run train timer (no limit until 'q')")
345
+ parser.add_argument("--studystatus", action="store_true", help="Show study daily table and SD")
346
+ parser.add_argument("--trainstatus", action="store_true", help="Show train daily table and SD")
347
+ parser.add_argument("--tables", action="store_true", help="Export tables.md to current directory")
348
+ parser.add_argument("duration", nargs="?", help="Optional duration e.g. 1h30m (if not using --study/--train)")
349
+ args = parser.parse_args()
350
+
351
+ # priority actions
352
+ if args.studystatus:
353
+ print_activity_table("study")
354
+ return
355
+ if args.trainstatus:
356
+ print_activity_table("train")
357
+ return
358
+ if args.tables:
359
+ out = write_tables_md("tables.md")
360
+ print(f"created {out}")
361
+ return
362
+
363
+ if args.study:
364
+ run_activity("study")
365
+ return
366
+ if args.train:
367
+ run_activity("train")
368
+ return
369
+
370
+ # fallback: duration countdown (compat)
371
+ if args.duration:
372
+ try:
373
+ target_seconds = parse_time_input(args.duration)
374
+ except ValueError as e:
375
+ print(e)
376
+ sys.exit(1)
377
+ run_countdown(target_seconds, args.duration)
378
+ else:
379
+ parser.print_help()
380
+
381
+
382
+ if __name__ == "__main__":
383
+ try:
384
+ main()
385
+ finally:
386
+ restore_terminal()
387
+
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: tm-timer
3
+ Version: 2.0
4
+ Summary: Minimalistic terminal-based timer with Study/Train tracking
5
+ Author: ph4nt01
6
+ Author-email: Ph4nt01 <ph4nt0.84@email.com>
7
+ License: MIT
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Dynamic: author
12
+ Dynamic: license-file
13
+ Dynamic: requires-python
14
+
15
+ # In The Name of God
16
+
17
+ ---
18
+
19
+ ## ⏱ tm : Minimalistic Terminal-based timer with Study/Train tracking
20
+
21
+ ### Features:
22
+
23
+ - Run from terminal:
24
+ - Countdown timer: `tm 10s`, `tm 25m`, `tm 1h30m`
25
+ - Study session: `tm --study` (tracks across sessions, resets daily)
26
+ - Train session: `tm --train` (tracks across sessions, resets daily)
27
+ - Daily study/train totals saved in `~/.tm_timer/data.json`
28
+ - Tables and statistics:
29
+ - `tm --studystatus` / `tm --trainstatus` → show daily table and Standard Deviation
30
+ - `tm --tables` → export `tables.md`
31
+ - Statistics:
32
+ - Average daily hours
33
+ - Standard Deviation of daily hours
34
+ - Status report (Very Good / Good / Not Good)
35
+ - Controls:
36
+ - Press `p` to pause/resume
37
+ - Press `q` to quit early
38
+ - Supports human durations like `2h45m30s`
39
+ - Simple, no graphical BLOAT.
40
+
41
+ ---
42
+
43
+ ### Installation:
44
+
45
+ ```bash
46
+ sudo apt install pipx # if not installed
47
+ pipx install tm-timer # to install tm-timer
48
+ pipx ensurepath # to add tm to PATH
49
+ exec $SHELL # reload shell
50
+
51
+ #or run locally with using cli.py
52
+ python3 cli.py 3h40m
53
+ ```
54
+
55
+ Example Usage:
56
+ bash
57
+ Copy code
58
+ tm --study
59
+ # Timer starts, press 'q' to stop, progress saved daily
60
+
61
+ tm --studystatus
62
+ # Shows study table and stats
63
+
64
+ tm --tables
65
+ # Exports study/train tables as tables.md
66
+ ⏱ tm preview:
67
+
@@ -1,4 +1,6 @@
1
+ LICENSE
1
2
  README.md
3
+ pyproject.toml
2
4
  setup.py
3
5
  tm_timer/__init__.py
4
6
  tm_timer/cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tm = tm_timer.cli:main
tm_timer-1.0.1/PKG-INFO DELETED
@@ -1,13 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: tm-timer
3
- Version: 1.0.1
4
- Summary: Terminal timer with pause/resume and alert
5
- Author: ph4nt01
6
- Classifier: Programming Language :: Python :: 3
7
- Classifier: Environment :: Console
8
- Classifier: Operating System :: POSIX :: Linux
9
- Requires-Python: >=3.6
10
- Dynamic: author
11
- Dynamic: classifier
12
- Dynamic: requires-python
13
- Dynamic: summary
tm_timer-1.0.1/README.md DELETED
@@ -1,24 +0,0 @@
1
- # In The Name of God
2
-
3
- ## tm ⏱ Terminal Timer
4
-
5
- Minimalistic Terminal-based timer.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pip install git+https://github.com/Ph4nt01/timer.git
11
- ```
12
-
13
- ## Usage
14
-
15
- ```bash
16
- tm 10s
17
- tm 25m
18
- tm 1h30m45s
19
-
20
- p — Pause/Resume
21
- q — Quit early
22
-
23
- ```
24
-
@@ -1,102 +0,0 @@
1
- import time
2
- import threading
3
- import sys
4
- import termios
5
- import tty
6
- import re
7
- import os
8
- import atexit
9
- import shutil
10
- import argparse
11
-
12
- original_term_settings = termios.tcgetattr(sys.stdin.fileno())
13
-
14
- def restore_terminal():
15
- try:
16
- termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, original_term_settings)
17
- except:
18
- pass
19
-
20
- atexit.register(restore_terminal)
21
-
22
- def with_raw_mode():
23
- fd = sys.stdin.fileno()
24
- old_settings = termios.tcgetattr(fd)
25
- tty.setraw(fd)
26
- return old_settings
27
-
28
- def get_char():
29
- fd = sys.stdin.fileno()
30
- old_settings = with_raw_mode()
31
- try:
32
- ch = sys.stdin.read(1)
33
- finally:
34
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
35
- return ch
36
-
37
- def parse_time_input(user_input):
38
- pattern = r"(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?"
39
- match = re.fullmatch(pattern, user_input.strip().lower())
40
- if not match or not any(match.groups()):
41
- raise ValueError("Invalid time format. Use format like '1h30m45s'.")
42
- hours = int(match.group(1)) if match.group(1) else 0
43
- minutes = int(match.group(2)) if match.group(2) else 0
44
- seconds = int(match.group(3)) if match.group(3) else 0
45
- return hours * 3600 + minutes * 60 + seconds
46
-
47
- def timer():
48
- global paused, running, seconds, target_seconds
49
- while running:
50
- if not paused:
51
- cols = shutil.get_terminal_size((80, 20)).columns
52
- mins, secs = divmod(seconds, 60)
53
- hours, mins = divmod(mins, 60)
54
- msg = f"Timer: {hours:02d}:{mins:02d}:{secs:02d} [Press 'p' to Pause/Resume, 'q' to Quit]"
55
- msg = msg[:cols - 1] if len(msg) >= cols else msg
56
- sys.stdout.write(f"\r\033[K{msg}")
57
- sys.stdout.flush()
58
- seconds += 1
59
- if seconds >= target_seconds:
60
- sys.stdout.write("\n\n⏰ Time's up! Target of {} reached.\n".format(target_input))
61
- sys.stdout.flush()
62
- running = False
63
- break
64
- time.sleep(1)
65
-
66
- def input_listener():
67
- global paused, running
68
- while running:
69
- key = get_char()
70
- if key.lower() == 'p':
71
- paused = not paused
72
- elif key.lower() == 'q':
73
- running = False
74
- break
75
-
76
- def entry():
77
- global paused, running, seconds, target_seconds, target_input
78
-
79
- parser = argparse.ArgumentParser(description="Terminal Timer with pause and alert.")
80
- parser.add_argument("duration", help="Timer duration (e.g., 4h, 25m, 1h30m45s)")
81
- args = parser.parse_args()
82
-
83
- try:
84
- target_input = args.duration
85
- target_seconds = parse_time_input(target_input)
86
- except ValueError as e:
87
- print(e)
88
- sys.exit(1)
89
-
90
- paused = False
91
- running = True
92
- seconds = 0
93
-
94
- input_thread = threading.Thread(target=input_listener, daemon=True)
95
- timer_thread = threading.Thread(target=timer)
96
-
97
- input_thread.start()
98
- timer_thread.start()
99
-
100
- timer_thread.join()
101
- restore_terminal()
102
- print("Timer stopped.")
@@ -1,13 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: tm-timer
3
- Version: 1.0.1
4
- Summary: Terminal timer with pause/resume and alert
5
- Author: ph4nt01
6
- Classifier: Programming Language :: Python :: 3
7
- Classifier: Environment :: Console
8
- Classifier: Operating System :: POSIX :: Linux
9
- Requires-Python: >=3.6
10
- Dynamic: author
11
- Dynamic: classifier
12
- Dynamic: requires-python
13
- Dynamic: summary
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- tm = tm_timer.cli:entry
File without changes
File without changes