tm-timer 1.0.1__tar.gz → 2.1__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.1/PKG-INFO ADDED
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: tm-timer
3
+ Version: 2.1
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
+ Dynamic: author
11
+ Dynamic: requires-python
12
+
13
+ # In The Name of God
14
+
15
+ ---
16
+ # ⏱️ tm-timer
17
+ >Minimalistic Terminal timer with Study session tracking
18
+
19
+ ## Features
20
+
21
+ - Countdown timer:
22
+ - `tm 10s`, `tm 25m`, `tm 1h30m`
23
+ - Study/Train modes:
24
+ - `tm --study` → track study sessions (resets daily, accumulates across multiple runs)
25
+ - `tm --train` → track training sessions (same behavior, separate table)
26
+ - Daily logging:
27
+ - Records totals in `~/.tm_timer/data.json`
28
+ - Each day resets at midnight
29
+ - Statistics:
30
+ - Average daily hours
31
+ - Standard Deviation of Time
32
+ - Status report:
33
+ - **Very Good** (SD < AVG/2)
34
+ - **Good** (SD = AVG/2)
35
+ - **Not Good** (SD > AVG/2)
36
+ - Export:
37
+ - `tm --studystatus` → show study table + stats
38
+ - `tm --trainstatus` → show train table + stats
39
+ - `tm --tables` → export both tables to `tables.md`
40
+ - Controls:
41
+ - `p` → pause/resume
42
+ - `q` → quit and save
43
+
44
+ ---
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ sudo apt install pipx # if not installed
50
+ pipx install tm-timer # install tm-timer
51
+ pipx ensurepath # add tm to PATH
52
+ exec $SHELL # reload shell
53
+ ````
54
+
55
+ ---
56
+
57
+ ## Example Usage
58
+
59
+ ```bash
60
+ # Start a study session
61
+ tm --study
62
+
63
+ # Check study stats
64
+ tm --studystatus
65
+
66
+ # Start a train session
67
+ tm --train
68
+
69
+ # Export tables to tables.md
70
+ tm --tables
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Example Tables Output
76
+
77
+ ```markdown
78
+ # tm_timer Tables
79
+
80
+ ---
81
+
82
+ ## STUDY Table
83
+
84
+ | Date | Day | Time |
85
+ | ---------- | --- | ------ |
86
+ | 2025-10-03 | FRI | 05:00:00 |
87
+ | 2025-10-02 | THU | 02:50:00 |
88
+ | 2025-09-29 | MON | 04:05:00 |
89
+ | 2025-09-27 | SAT | 03:36:00 |
90
+
91
+ **[AVG] = 3.98h [Standard Deviation of Time] = 0.75h Status = Very Good**
92
+
93
+ ---
94
+
95
+ ## TRAIN Table
96
+
97
+ | Date | Day | Time |
98
+ | ---------- | --- | ------ |
99
+ | 2025-10-03 | FRI | 00:57:00 |
100
+ | 2025-10-02 | THU | 00:50:00 |
101
+ | 2025-09-30 | SUN | 00:35:00 |
102
+ | 2025-09-28 | SAT | 01:36:00 |
103
+
104
+ **[AVG] = 1.05h [Standard Deviation of Time] = 0.38h Status = Very Good**
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Preview
110
+
111
+
112
+ ![Timer preview](demo1.gif)
113
+
114
+ ![Timer preview](demo2.gif)
115
+
116
+ ![Timer preview](demo3.gif)
tm_timer-2.1/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # In The Name of God
2
+
3
+ ---
4
+ # ⏱️ tm-timer
5
+ >Minimalistic Terminal timer with Study session tracking
6
+
7
+ ## Features
8
+
9
+ - Countdown timer:
10
+ - `tm 10s`, `tm 25m`, `tm 1h30m`
11
+ - Study/Train modes:
12
+ - `tm --study` → track study sessions (resets daily, accumulates across multiple runs)
13
+ - `tm --train` → track training sessions (same behavior, separate table)
14
+ - Daily logging:
15
+ - Records totals in `~/.tm_timer/data.json`
16
+ - Each day resets at midnight
17
+ - Statistics:
18
+ - Average daily hours
19
+ - Standard Deviation of Time
20
+ - Status report:
21
+ - **Very Good** (SD < AVG/2)
22
+ - **Good** (SD = AVG/2)
23
+ - **Not Good** (SD > AVG/2)
24
+ - Export:
25
+ - `tm --studystatus` → show study table + stats
26
+ - `tm --trainstatus` → show train table + stats
27
+ - `tm --tables` → export both tables to `tables.md`
28
+ - Controls:
29
+ - `p` → pause/resume
30
+ - `q` → quit and save
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ sudo apt install pipx # if not installed
38
+ pipx install tm-timer # install tm-timer
39
+ pipx ensurepath # add tm to PATH
40
+ exec $SHELL # reload shell
41
+ ````
42
+
43
+ ---
44
+
45
+ ## Example Usage
46
+
47
+ ```bash
48
+ # Start a study session
49
+ tm --study
50
+
51
+ # Check study stats
52
+ tm --studystatus
53
+
54
+ # Start a train session
55
+ tm --train
56
+
57
+ # Export tables to tables.md
58
+ tm --tables
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Example Tables Output
64
+
65
+ ```markdown
66
+ # tm_timer Tables
67
+
68
+ ---
69
+
70
+ ## STUDY Table
71
+
72
+ | Date | Day | Time |
73
+ | ---------- | --- | ------ |
74
+ | 2025-10-03 | FRI | 05:00:00 |
75
+ | 2025-10-02 | THU | 02:50:00 |
76
+ | 2025-09-29 | MON | 04:05:00 |
77
+ | 2025-09-27 | SAT | 03:36:00 |
78
+
79
+ **[AVG] = 3.98h [Standard Deviation of Time] = 0.75h Status = Very Good**
80
+
81
+ ---
82
+
83
+ ## TRAIN Table
84
+
85
+ | Date | Day | Time |
86
+ | ---------- | --- | ------ |
87
+ | 2025-10-03 | FRI | 00:57:00 |
88
+ | 2025-10-02 | THU | 00:50:00 |
89
+ | 2025-09-30 | SUN | 00:35:00 |
90
+ | 2025-09-28 | SAT | 01:36:00 |
91
+
92
+ **[AVG] = 1.05h [Standard Deviation of Time] = 0.38h Status = Very Good**
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Preview
98
+
99
+
100
+ ![Timer preview](demo1.gif)
101
+
102
+ ![Timer preview](demo2.gif)
103
+
104
+ ![Timer preview](demo3.gif)
@@ -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.1"
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.1",
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"[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("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,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: tm-timer
3
+ Version: 2.1
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
+ Dynamic: author
11
+ Dynamic: requires-python
12
+
13
+ # In The Name of God
14
+
15
+ ---
16
+ # ⏱️ tm-timer
17
+ >Minimalistic Terminal timer with Study session tracking
18
+
19
+ ## Features
20
+
21
+ - Countdown timer:
22
+ - `tm 10s`, `tm 25m`, `tm 1h30m`
23
+ - Study/Train modes:
24
+ - `tm --study` → track study sessions (resets daily, accumulates across multiple runs)
25
+ - `tm --train` → track training sessions (same behavior, separate table)
26
+ - Daily logging:
27
+ - Records totals in `~/.tm_timer/data.json`
28
+ - Each day resets at midnight
29
+ - Statistics:
30
+ - Average daily hours
31
+ - Standard Deviation of Time
32
+ - Status report:
33
+ - **Very Good** (SD < AVG/2)
34
+ - **Good** (SD = AVG/2)
35
+ - **Not Good** (SD > AVG/2)
36
+ - Export:
37
+ - `tm --studystatus` → show study table + stats
38
+ - `tm --trainstatus` → show train table + stats
39
+ - `tm --tables` → export both tables to `tables.md`
40
+ - Controls:
41
+ - `p` → pause/resume
42
+ - `q` → quit and save
43
+
44
+ ---
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ sudo apt install pipx # if not installed
50
+ pipx install tm-timer # install tm-timer
51
+ pipx ensurepath # add tm to PATH
52
+ exec $SHELL # reload shell
53
+ ````
54
+
55
+ ---
56
+
57
+ ## Example Usage
58
+
59
+ ```bash
60
+ # Start a study session
61
+ tm --study
62
+
63
+ # Check study stats
64
+ tm --studystatus
65
+
66
+ # Start a train session
67
+ tm --train
68
+
69
+ # Export tables to tables.md
70
+ tm --tables
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Example Tables Output
76
+
77
+ ```markdown
78
+ # tm_timer Tables
79
+
80
+ ---
81
+
82
+ ## STUDY Table
83
+
84
+ | Date | Day | Time |
85
+ | ---------- | --- | ------ |
86
+ | 2025-10-03 | FRI | 05:00:00 |
87
+ | 2025-10-02 | THU | 02:50:00 |
88
+ | 2025-09-29 | MON | 04:05:00 |
89
+ | 2025-09-27 | SAT | 03:36:00 |
90
+
91
+ **[AVG] = 3.98h [Standard Deviation of Time] = 0.75h Status = Very Good**
92
+
93
+ ---
94
+
95
+ ## TRAIN Table
96
+
97
+ | Date | Day | Time |
98
+ | ---------- | --- | ------ |
99
+ | 2025-10-03 | FRI | 00:57:00 |
100
+ | 2025-10-02 | THU | 00:50:00 |
101
+ | 2025-09-30 | SUN | 00:35:00 |
102
+ | 2025-09-28 | SAT | 01:36:00 |
103
+
104
+ **[AVG] = 1.05h [Standard Deviation of Time] = 0.38h Status = Very Good**
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Preview
110
+
111
+
112
+ ![Timer preview](demo1.gif)
113
+
114
+ ![Timer preview](demo2.gif)
115
+
116
+ ![Timer preview](demo3.gif)
@@ -1,4 +1,5 @@
1
1
  README.md
2
+ pyproject.toml
2
3
  setup.py
3
4
  tm_timer/__init__.py
4
5
  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