Compare commits
6 Commits
bf76c368c9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 27603536a8 | |||
| 5f55aa32fa | |||
| 0e48ce265c | |||
| a54b19b938 | |||
| bb5364d021 | |||
| 6fa1c5a4c3 |
107
main.py
107
main.py
@@ -1,95 +1,62 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import argparse
|
from util.str_to_datetime import str_to_datetime
|
||||||
from lib.DataStore import DataStore
|
|
||||||
from lib.TimeSlot import TimeSlot
|
|
||||||
from lib.ui import convert_to_table
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
from model.TimeSlotContainer import time_slot_container as tsc
|
||||||
|
from model.TimeSlot import TimeSlot
|
||||||
|
|
||||||
def create_parser():
|
from util.views import convert_to_table
|
||||||
# Create the top-level parser
|
from util.parser import create_parser
|
||||||
parser = argparse.ArgumentParser(description="Time tracker.")
|
from util import timeslotlist
|
||||||
subparsers = parser.add_subparsers(dest="command", help="Sub-command help")
|
|
||||||
|
|
||||||
# Create the parser for the "ls" command
|
|
||||||
_ = subparsers.add_parser("ls", help="List all events.")
|
|
||||||
|
|
||||||
# Create the parser for the "ps" command
|
|
||||||
_ = subparsers.add_parser("ps", help="Show the currently running event.")
|
|
||||||
|
|
||||||
# Create the parser for the "add" command
|
|
||||||
parser_add = subparsers.add_parser("add", help="Adding a new event.")
|
|
||||||
parser_add.add_argument("name", help="Name of the event")
|
|
||||||
parser_add.add_argument("-s", "--start", help="Start datetime (default now)")
|
|
||||||
parser_add.add_argument("-e", "--end", help="End datetime")
|
|
||||||
|
|
||||||
# Create the parser for the "start" command
|
|
||||||
parser_start = subparsers.add_parser("start", help="Starting a new event.")
|
|
||||||
parser_start.add_argument("name", help="Name of the event")
|
|
||||||
parser_start.add_argument("-s", "--start", help="Start datetime (default now)")
|
|
||||||
|
|
||||||
# Create the parser for the "end" command
|
|
||||||
parser_end = subparsers.add_parser("end", help="Ending the current event.")
|
|
||||||
parser_end.add_argument("-e", "--end", help="End datetime")
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = create_parser()
|
parser = create_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
ds = DataStore("data.json")
|
if args.command in ["ls", "acc"]:
|
||||||
|
match args.span:
|
||||||
|
case "w":
|
||||||
|
start_of_week = datetime.now() - timedelta(days=datetime.now().weekday())
|
||||||
|
start_of_week_midnight = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
slots = tsc.get_time_slots_by_date(start=start_of_week_midnight)
|
||||||
|
case "lw":
|
||||||
|
start_of_week = datetime.now() - timedelta(days=datetime.now().weekday())
|
||||||
|
start_of_week_midnight = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
slots = tsc.get_time_slots_by_date(
|
||||||
|
start=start_of_week_midnight - timedelta(days=7),
|
||||||
|
end=start_of_week_midnight,
|
||||||
|
)
|
||||||
|
case "d":
|
||||||
|
last_midnight = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
slots = tsc.get_time_slots_by_date(start=last_midnight)
|
||||||
|
case _:
|
||||||
|
slots = tsc.get_all_time_slots()
|
||||||
|
|
||||||
if args.command == "ls":
|
if args.command == "ls":
|
||||||
print(convert_to_table(ds.get_all_time_slots()))
|
print(convert_to_table(slots))
|
||||||
|
|
||||||
elif args.command == "start":
|
elif args.command == "start":
|
||||||
time_slots = ds.get_all_time_slots()
|
|
||||||
running = list(filter(lambda x: x.end is None, time_slots))
|
|
||||||
if len(running):
|
|
||||||
print("An event is already running.")
|
|
||||||
else:
|
|
||||||
if args.start is not None:
|
if args.start is not None:
|
||||||
s = datetime.strptime(args.start, '%d.%m.%y %H:%M')
|
tsc.open_time_slot(args.name, str_to_datetime(args.start))
|
||||||
ds.add_time_slot(TimeSlot(args.name, start=s))
|
|
||||||
else:
|
else:
|
||||||
ds.add_time_slot(TimeSlot(args.name))
|
tsc.open_time_slot(args.name)
|
||||||
print(f"Started event {args.name}.")
|
print(f"Started event {args.name}.")
|
||||||
|
|
||||||
elif args.command == "end":
|
elif args.command == "end":
|
||||||
time_slots = ds.get_all_time_slots()
|
|
||||||
running = list(filter(lambda x: x.end is None, time_slots))
|
|
||||||
if len(running) <= 0:
|
|
||||||
print("No running event.")
|
|
||||||
elif len(running) > 1:
|
|
||||||
raise Exception("Found multiple running event.")
|
|
||||||
else:
|
|
||||||
if args.end is not None:
|
if args.end is not None:
|
||||||
s = datetime.strptime(args.end, '%d.%m.%y %H:%M')
|
tsc.close_time_slot(str_to_datetime(args.end))
|
||||||
running[0].end = s
|
|
||||||
else:
|
else:
|
||||||
running[0].end_now()
|
tsc.close_time_slot()
|
||||||
ds.write_update()
|
print("Ended event.")
|
||||||
print(f"Ended event {running[0].name}.")
|
|
||||||
|
|
||||||
elif args.command == "ps":
|
elif args.command == "ps":
|
||||||
time_slots = ds.get_all_time_slots()
|
print(convert_to_table(tsc.get_open_time_slots()))
|
||||||
running = list(filter(lambda x: x.end is None, time_slots))
|
|
||||||
print(convert_to_table(running))
|
|
||||||
|
|
||||||
elif args.command == "add":
|
elif args.command == "add":
|
||||||
time_slots = ds.get_all_time_slots()
|
tsc.add_time_slot(args.name, str_to_datetime(args.start), str_to_datetime(args.end))
|
||||||
running = list(filter(lambda x: x.end is None, time_slots))
|
|
||||||
if len(running) > 0 and args.end is None:
|
elif args.command == "acc":
|
||||||
print("An event is already running.")
|
d = timeslotlist.accumulate_duration(timeslotlist.filter(slots, args.query))
|
||||||
else:
|
print(f"{d.seconds // 3600}:{d.seconds % 3600 // 60:02}")
|
||||||
ts = TimeSlot(args.name)
|
|
||||||
if args.start is not None:
|
|
||||||
ts.start = datetime.strptime(args.start, '%d.%m.%y %H:%M')
|
|
||||||
if args.end is not None:
|
|
||||||
ts.end = datetime.strptime(args.end, '%d.%m.%y %H:%M')
|
|
||||||
ds.add_time_slot(ts)
|
|
||||||
ds.write_update()
|
|
||||||
print("The event was added.")
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .TimeSlot import TimeSlot
|
from model.TimeSlot import TimeSlot
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
@@ -30,11 +30,9 @@ class DataStore:
|
|||||||
|
|
||||||
def add_time_slot(self, time_slot: TimeSlot):
|
def add_time_slot(self, time_slot: TimeSlot):
|
||||||
self._time_slots.append(time_slot)
|
self._time_slots.append(time_slot)
|
||||||
self.write_update()
|
|
||||||
|
|
||||||
def remove_time_slot(self, time_slot: TimeSlot):
|
def remove_time_slot(self, time_slot: TimeSlot):
|
||||||
self._time_slots.remove(time_slot)
|
self._time_slots.remove(time_slot)
|
||||||
self.write_update()
|
|
||||||
|
|
||||||
def get_all_time_slots(self):
|
def get_all_time_slots(self) -> list[TimeSlot]:
|
||||||
return self._time_slots[:]
|
return self._time_slots[:]
|
||||||
@@ -42,3 +42,8 @@ class TimeSlot():
|
|||||||
|
|
||||||
def end_now(self):
|
def end_now(self):
|
||||||
self.end = datetime.now()
|
self.end = datetime.now()
|
||||||
|
|
||||||
|
def duration(self) -> datetime.timedelta:
|
||||||
|
end = self.end
|
||||||
|
if not end: end = datetime.now()
|
||||||
|
return end - self.start
|
||||||
51
model/TimeSlotContainer.py
Normal file
51
model/TimeSlotContainer.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from model.DataStore import DataStore
|
||||||
|
from model.TimeSlot import TimeSlot
|
||||||
|
|
||||||
|
|
||||||
|
class TimeSlotContainer:
|
||||||
|
|
||||||
|
def __init__(self, ds: DataStore):
|
||||||
|
self._ds = ds
|
||||||
|
|
||||||
|
def get_open_time_slots(self) -> list[TimeSlot]:
|
||||||
|
return [ts for ts in self._ds.get_all_time_slots() if ts.end is None]
|
||||||
|
|
||||||
|
def get_all_time_slots(self) -> list[TimeSlot]:
|
||||||
|
return self._ds.get_all_time_slots()
|
||||||
|
|
||||||
|
def get_time_slots_by_date(self, start: datetime, end: datetime = datetime.now()):
|
||||||
|
# The selection will be oriented at the start date
|
||||||
|
return [ts for ts in self._ds.get_all_time_slots()
|
||||||
|
if ts.start >= start and ts.start <= end]
|
||||||
|
|
||||||
|
def open_time_slot(self, name: str, start_dt: datetime = datetime.now()):
|
||||||
|
if len(self.get_open_time_slots()) > 0:
|
||||||
|
raise Exception("Ein event ist bereits aktiv.")
|
||||||
|
ts = TimeSlot(name)
|
||||||
|
ts.start = start_dt
|
||||||
|
self._ds.add_time_slot(ts)
|
||||||
|
self._ds.write_update()
|
||||||
|
|
||||||
|
def close_time_slot(self, end_dt: datetime = datetime.now()):
|
||||||
|
ts = self.get_open_time_slots()[0]
|
||||||
|
self._ds.remove_time_slot(ts)
|
||||||
|
ts.end = end_dt
|
||||||
|
self._ds.add_time_slot(ts)
|
||||||
|
self._ds.write_update()
|
||||||
|
|
||||||
|
def add_time_slot(self, name: str, start_dt: datetime, end_dt: datetime):
|
||||||
|
self.open_time_slot(name, start_dt)
|
||||||
|
self.close_time_slot(end_dt)
|
||||||
|
|
||||||
|
def update_time_slot(self, old: TimeSlot, new: TimeSlot):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def delete_time_slot(self, ts: TimeSlot):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
time_slot_container = TimeSlotContainer(DataStore("data.json"))
|
||||||
|
except FileNotFoundError:
|
||||||
|
time_slot_container = TimeSlotContainer(DataStore("data.json", create=True))
|
||||||
36
util/parser.py
Normal file
36
util/parser.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def create_parser():
|
||||||
|
# Create the top-level parser
|
||||||
|
parser = argparse.ArgumentParser(description="Time tracker.")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="Sub-command help")
|
||||||
|
|
||||||
|
# Create the parser for the "ls" command
|
||||||
|
parser_ls = subparsers.add_parser("ls", help="List events.")
|
||||||
|
parser_ls.add_argument("-s", "--span", choices=['d', 'w', 'lw'], help="Display only envent in a certain time span (d = current day; w = current week; lw = last week)")
|
||||||
|
|
||||||
|
# Create the parser for the "ps" command
|
||||||
|
_ = subparsers.add_parser("ps", help="Show the currently running event.")
|
||||||
|
|
||||||
|
# Create the parser for the "add" command
|
||||||
|
parser_add = subparsers.add_parser("add", help="Adding a new event.")
|
||||||
|
parser_add.add_argument("name", help="Name of the event")
|
||||||
|
parser_add.add_argument("-s", "--start", required=True, help="Start datetime (default now)")
|
||||||
|
parser_add.add_argument("-e", "--end", required=True, help="End datetime")
|
||||||
|
|
||||||
|
# Create the parser for the "start" command
|
||||||
|
parser_start = subparsers.add_parser("start", help="Starting a new event.")
|
||||||
|
parser_start.add_argument("name", help="Name of the event")
|
||||||
|
parser_start.add_argument("-s", "--start", help="Start datetime (default now)")
|
||||||
|
|
||||||
|
# Create the parser for the "end" command
|
||||||
|
parser_end = subparsers.add_parser("end", help="Ending the current event.")
|
||||||
|
parser_end.add_argument("-e", "--end", help="End datetime")
|
||||||
|
|
||||||
|
# Create the parser for the "acc" command
|
||||||
|
parser_end = subparsers.add_parser("acc", help="Accumulate the duration of events.")
|
||||||
|
parser_end.add_argument("query", help="The regex matching for the name of the event.")
|
||||||
|
parser_end.add_argument("-s", "--span", choices=['d', 'w', 'lw'], help="Display only envent in a certain time span (d = current day; w = current week; lw = last week)")
|
||||||
|
|
||||||
|
return parser
|
||||||
19
util/str_to_datetime.py
Normal file
19
util/str_to_datetime.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_datetime(s: str) -> dt:
|
||||||
|
s = s.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return dt.strptime(s, '%d.%m.%y %H:%M')
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
tim = dt.strptime(s, '%H:%M').time()
|
||||||
|
dat = dt.now().date()
|
||||||
|
return dt.combine(dat, tim)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise ValueError("The given string can not be interpreted as a datetime.")
|
||||||
12
util/timeslotlist.py
Normal file
12
util/timeslotlist.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from model.TimeSlot import TimeSlot
|
||||||
|
|
||||||
|
|
||||||
|
def accumulate_duration(timeslots: list[TimeSlot]) -> timedelta:
|
||||||
|
return sum([ts.duration() for ts in timeslots], start=timedelta(0))
|
||||||
|
|
||||||
|
def filter(timeslots: list[TimeSlot], query: str) -> list[TimeSlot]:
|
||||||
|
pattern = re.compile(query)
|
||||||
|
return [ts for ts in timeslots if pattern.search(ts.name)]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from .TimeSlot import TimeSlot
|
from model.TimeSlot import TimeSlot
|
||||||
|
|
||||||
|
|
||||||
def convert_to_table(time_slot_list: list[TimeSlot]):
|
def convert_to_table(time_slot_list: list[TimeSlot]) -> str:
|
||||||
widths = [30, 26, 26]
|
widths = [30, 26, 26]
|
||||||
|
|
||||||
r_str = f"+{'-'*(widths[0]+2)}+{'-'*(widths[1]+2)}+{'-'*(widths[2]+2)}+\n"
|
r_str = f"+{'-'*(widths[0]+2)}+{'-'*(widths[1]+2)}+{'-'*(widths[2]+2)}+\n"
|
||||||
Reference in New Issue
Block a user