-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathflowmodoro.py
More file actions
executable file
·158 lines (123 loc) · 4 KB
/
Copy pathflowmodoro.py
File metadata and controls
executable file
·158 lines (123 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
"""
Flowmodoro CLI — A minimalist productivity timer based on flowmodoro technique
(work + break cycles, with adaptive break durations).
Usage:
flowmodoro start # Start a work session
flowmodoro break # End work and begin a proportional break
flowmodoro status # Show current session status
Author: Luis RodVar
License: MIT
"""
import argparse
import json
import sys
import time
from datetime import timedelta
from pathlib import Path
# Path to the temporary state file
STATE_FILE = Path("/tmp/flowmodoro_state.json")
def save_state(state: dict) -> None:
"""Save the current session state to a temporary file."""
STATE_FILE.write_text(json.dumps(state))
def delete_state() -> None:
"""Delete the session state file if it exists."""
if STATE_FILE.exists():
STATE_FILE.unlink()
def load_state() -> dict | None:
"""
Load the session state from the JSON file.
Returns:
dict: The current session state if valid.
None: If the file does not exist or is invalid.
"""
if not STATE_FILE.exists():
return None
try:
content = STATE_FILE.read_text().strip()
if not content:
raise json.JSONDecodeError("Empty file", "", 0)
return json.loads(content)
except (json.JSONDecodeError, OSError):
delete_state()
return None
def format_duration(seconds: float) -> str:
"""Convert seconds to a HH:MM:SS formatted string."""
return str(timedelta(seconds=int(seconds)))
def start() -> None:
"""Start a new work session."""
now = time.time()
save_state({"mode": "work", "start": now})
print("Work session started.")
def take_break() -> None:
"""
End the current work session and calculate a break duration
based on time worked. Saves the new break state.
"""
state = load_state()
if state is None:
print("No active session.")
return
if state.get("mode") != "work":
print("Not in work mode.", file=sys.stderr)
return
start_time = state["start"]
now = time.time()
minutes_worked = (now - start_time) / 60
# Break duration is 20% of time worked, minimum 1 minute
break_minutes = max(1, int(minutes_worked * 0.2))
break_seconds = break_minutes * 60
save_state({
"mode": "break",
"break_start": now,
"break_duration": break_seconds
})
print(f"Work session ended. Duration: {int(minutes_worked)} minutes")
print(f"Starting break for {break_minutes} minutes.")
def status() -> None:
"""
Display the current session status:
- If in work mode, show time worked.
- If in break mode, show time remaining.
- If break is over, clear the session.
"""
state = load_state()
if state is None:
print("No active session.")
return
now = time.time()
if state["mode"] == "work":
elapsed = now - state["start"]
print("mode=work")
print(f"worked_seconds={int(elapsed)}")
print(f"worked_hms={format_duration(elapsed)}")
elif state["mode"] == "break":
elapsed = now - state["break_start"]
remaining = max(0, state["break_duration"] - elapsed)
if remaining == 0:
delete_state()
print("Break ended.")
return
print("mode=break")
print(f"remaining_seconds={int(remaining)}")
print(f"remaining_hms={format_duration(remaining)}")
else:
print("Unknown state.", file=sys.stderr)
def main() -> None:
"""Parse command-line arguments and dispatch to the appropriate handler."""
parser = argparse.ArgumentParser(description="Flowmodoro timer CLI")
parser.add_argument(
"command",
choices=["start", "break", "status"],
help="Command to execute"
)
args = parser.parse_args()
match args.command:
case "start":
start()
case "break":
take_break()
case "status":
status()
if __name__ == "__main__":
main()