237 lines
6.8 KiB
Python
Executable File
237 lines
6.8 KiB
Python
Executable File
#!/bin/python3
|
|
|
|
import argparse
|
|
from datetime import datetime
|
|
from datetime import timedelta
|
|
from datetime import date
|
|
import os
|
|
import sys
|
|
import yaml
|
|
from pathlib import Path
|
|
from rofi import Rofi
|
|
import subprocess
|
|
import time
|
|
|
|
|
|
APPNAME = "StudyManager"
|
|
CONFIG_HOME = Path(os.environ["XDG_CONFIG_HOME"]) / APPNAME
|
|
CONFIG_FILE = CONFIG_HOME / "config"
|
|
CACHE_HOME = Path(os.environ["XDG_CACHE_HOME"]) / APPNAME
|
|
|
|
_config = None
|
|
|
|
parser = argparse.ArgumentParser(description="Helps to manage your studies.")
|
|
parser.add_argument(
|
|
"-c", "--chose", action="store_true", help="Chose new Current Course."
|
|
)
|
|
parser.add_argument(
|
|
"-l", "--list", action="store_true", help="List resources of Current Course."
|
|
)
|
|
parser.add_argument(
|
|
"-t",
|
|
"--timetable",
|
|
action="store_true",
|
|
help="Insert (/Update) Current Course's timetable into calendar.",
|
|
)
|
|
args = vars(parser.parse_args())
|
|
|
|
|
|
def config():
|
|
global _config
|
|
if _config is None:
|
|
|
|
if not CONFIG_FILE.exists():
|
|
print(f"Config file {CONFIG_FILE} does not exist. Please create it")
|
|
sys.exit(1)
|
|
|
|
with open(CONFIG_FILE, "r") as f:
|
|
_config = yaml.safe_load(f)
|
|
return _config
|
|
|
|
|
|
def onChose():
|
|
dst = Path(os.path.expandvars(config()["CURRENT_COURSE_LINK_DST"]))
|
|
coursesRootDir = Path(os.path.expandvars(config()["COURSES_ROOT_DIR"]))
|
|
|
|
courses = [x.stem for x in coursesRootDir.iterdir() if x.is_dir()]
|
|
|
|
index, key = Rofi().select("Select Current Course", courses)
|
|
|
|
# Return on aborted input
|
|
if key == -1:
|
|
return
|
|
|
|
src = coursesRootDir / courses[index]
|
|
|
|
assert src.exists()
|
|
|
|
try:
|
|
os.symlink(src, dst)
|
|
except FileExistsError:
|
|
os.remove(dst)
|
|
os.symlink(src, dst)
|
|
|
|
|
|
def onList():
|
|
import pyperclip
|
|
|
|
currentCourse = Path(os.path.expandvars(config()["CURRENT_COURSE_LINK_DST"]))
|
|
assert currentCourse.exists()
|
|
|
|
infoFile = currentCourse / config()["INFO_FILE"]
|
|
|
|
if not infoFile.exists():
|
|
print(
|
|
f"{infoFile} does not exists for course {currentCourse.stem}. There is nothing to list."
|
|
)
|
|
return
|
|
|
|
with open(infoFile, "r") as f:
|
|
content = yaml.safe_load(f)
|
|
|
|
contentList = [f"{key}: {value}" for (key, value) in content.items()]
|
|
|
|
index, key = Rofi().select("Select line to copy", contentList)
|
|
|
|
# Return on aborted input
|
|
if key == -1:
|
|
return
|
|
|
|
selectedVal = list(content.values())[index]
|
|
|
|
pyperclip.copy(selectedVal)
|
|
|
|
|
|
def onTimetable():
|
|
currentCourse = Path(os.path.expandvars(config()["CURRENT_COURSE_LINK_DST"]))
|
|
assert currentCourse.exists()
|
|
|
|
timetableFile = currentCourse / config()["TIMETABLE_FILE"]
|
|
infoFile = currentCourse / config()["INFO_FILE"]
|
|
|
|
if not timetableFile.exists():
|
|
print(
|
|
f"{timetableFile} does not exists for course {currentCourse.stem}. Not possible to create timetable."
|
|
)
|
|
return
|
|
|
|
if not infoFile.exists():
|
|
print(
|
|
f"{infoFile} does not exists for course {currentCourse.stem}. Not possible to create timetable."
|
|
)
|
|
return
|
|
|
|
with open(timetableFile, "r") as f:
|
|
timetables = list(yaml.safe_load(f).items())
|
|
|
|
with open(infoFile, "r") as f:
|
|
info = yaml.safe_load(f)
|
|
|
|
semesterStart = config()["SEMESTER_START"]
|
|
semesterEnd = config()["SEMESTER_END"]
|
|
semesterEnd = semesterEnd + timedelta(days=1) # khal until is exclusive
|
|
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
|
|
|
calendar = config()["CALENDAR"]
|
|
categories = f"{config()['SEMESTER']},{info['short']}" # No space between categories as they are removed anyways
|
|
|
|
# Check if timetable entry has already been created in the calendar
|
|
out = subprocess.run(
|
|
["khal", "search", f"--include-calendar={calendar}", categories],
|
|
stdout=subprocess.PIPE,
|
|
).stdout
|
|
|
|
if out not in [b"", b"\n"]:
|
|
print(f"Possibly colliding timetable entry exists: {out.decode('utf-8')}")
|
|
while True:
|
|
choice = input("Do you want to delete them? YES/NO\n").lower()
|
|
if choice in ["yes", "y"]:
|
|
# Handle interactive deletion prompt by khal (https://stackoverflow.com/a/50438585/5464989)
|
|
input_buffer = sys.stdin # a buffer to get the user input from
|
|
output_buffer = sys.stdout # a buffer to write khal's output to
|
|
proc = subprocess.Popen(
|
|
[
|
|
"khal",
|
|
"edit",
|
|
"--show-past",
|
|
f"--include-calendar{calendar}",
|
|
categories,
|
|
],
|
|
stdin=subprocess.PIPE, # pipe its STDIN so we can write to it
|
|
stdout=output_buffer, # pipe directly to the output_buffer
|
|
universal_newlines=True,
|
|
)
|
|
time.sleep(0.5) # give some time for khal to forward its stdout
|
|
print(
|
|
"", end="", file=output_buffer, flush=True
|
|
) # print the input prompt
|
|
print(
|
|
input_buffer.readline(), file=proc.stdin, flush=True
|
|
) # forward the user input
|
|
|
|
elif choice in ["no", "n"]:
|
|
break
|
|
else:
|
|
print("Please respond with 'yes' or 'no'")
|
|
|
|
# Iterate over all lectures/exercises and create entry for each of them
|
|
cmds = []
|
|
for (_, e) in timetables: # Drop key of tuple
|
|
|
|
title = f"{info['short']} {e['type']}"
|
|
location = e["location"]
|
|
until = semesterEnd.strftime("%d/%m/%Y")
|
|
firstOccurenceDate = semesterStart + timedelta(days=days.index(e["day"]))
|
|
startDate = firstOccurenceDate.strftime("%d/%m/%Y")
|
|
startTime = e["start"]
|
|
endTime = e["end"]
|
|
|
|
cmd = [
|
|
"khal",
|
|
"new",
|
|
f"--calendar={calendar}",
|
|
f"--location='{location}'",
|
|
f"--categories='{categories}'",
|
|
"--repeat=weekly",
|
|
f"--until={until}",
|
|
startDate,
|
|
startTime,
|
|
endTime,
|
|
title,
|
|
]
|
|
|
|
cmds.append(cmd)
|
|
|
|
while True:
|
|
commandList = "\n".join([" ".join(c) for c in cmds]) ## Prepare for printing
|
|
choice = input(
|
|
"Do you want to execute the following commands? YES/NO:\n"
|
|
+ commandList
|
|
+ "\n"
|
|
).lower()
|
|
if choice in ["yes", "y"]:
|
|
for c in cmds:
|
|
subprocess.call(c)
|
|
print("Done")
|
|
return
|
|
elif choice in ["no", "n"]:
|
|
print("Noting to do. Exiting.")
|
|
return
|
|
else:
|
|
print("Please respond with 'yes' or 'no'\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if args["chose"]:
|
|
onChose()
|
|
|
|
elif args["list"]:
|
|
onList()
|
|
|
|
elif args["timetable"]:
|
|
onTimetable()
|
|
|
|
else:
|
|
pass
|