decal/decal

268 lines
9.2 KiB
Python
Executable File

#!/usr/bin/python
import configparser
import datetime
import calendar
import caldav
import argparse
import os
import json
config = configparser.ConfigParser()
Version = "%(prog)s 0.1"
configpath = os.getenv("HOME")+"/.config/decal.conf"
config.read(configpath)
#define arguments
today = datetime.date.today()
parser = argparse.ArgumentParser(description="Cal with events.")
parser.add_argument("year",
action="store",
default=today.year,
nargs="?",
type=int)
parser.add_argument("month",
action="store",
default=today.month,
nargs="?",
type=int)
parser.add_argument("day",
action="store",
default=today.day,
nargs="?",
type=int)
parser.add_argument("-V", "--version",
action="version",
version=Version,
help="Display the version of the program")
parser.add_argument("--json",
action="store_true",
help="Dump events output to json")
parser.add_argument("--create",
action="store_true",
help="Create a new event")
parser.add_argument("--calendar",
action="append",
help="Specify a calendar (or multiple calendars) to sync from")
parser.add_argument("-1",
action="store_true",
help="show only a single month (default)")
parser.add_argument("-3",
action="store_true",
help="show three months spanning the date")
parser.add_argument("-y",
action="store_true",
help="show the whole year")
parser.add_argument("-n",
action="store",
type=int,
help="show n months")
args = vars(parser.parse_args())
# some stubs for now
if args["create"]:
print("Not implemented")
exit(0)
if args["json"]:
print("Not implemented")
exit(0)
#check some stuff, do some warnings, initiate the config, etc.
if not os.path.exists(configpath):
config['DEFAULT'] = {'uri': 'your caldav server here',
'user': 'your username here',
'password': 'your pass here'}
print("Creating an empty config in ~/.config/decal.conf")
with open(configpath,'w') as configfile:
config.write(configfile)
configfile.close()
print("To properly utilize decal, please fill out the fields in the config")
exit(1)
for arg in ("user","password","uri"):
if not arg in config['DEFAULT']:
print("The config is incomplete, please check the \""+arg+"\" field")
exit(1)
if config['DEFAULT']['uri'] == "your caldav server here":
print("To properly utilize decal, please fill out the fields in the config")
exit(1)
#actual works begins here
#generate the actual calendar, line by line, output an array of lines.
#it works trust me, idk what is happening in this one but it works.
def gencal(year,month,start_on_sunday=True,cell_modifier=lambda d: d,append_year=True):
firstweekday = 0
if start_on_sunday:
firstweekday = 6
cal = calendar.Calendar(firstweekday=firstweekday)
lines = [""]*6
monthstart = False
counter = 0
for date in cal.itermonthdates(year,month):
lines[counter//7]
day = str(date)[-2:]
if day == "01":
monthstart = not monthstart
if monthstart:
lines[counter//7] += cell_modifier(day)
else:
lines[counter//7] += " "
lines[counter//7] +=" "
counter+=1
month = datetime.date(year,month,1).strftime("%B %Y")
padding = (21-len(month))//2
rpadding = 21%(padding+len(month)+padding)
if start_on_sunday:
lines.insert(0,"Su Mo Tu We Th Fr Sa ")
else:
lines.insert(0,"Mo Tu We Th Fr Sa Su ")
lines.insert(0,(" "*padding)+month+(" "*(padding+rpadding)))
lines[-1] += " "*(21-len(lines[-1]))
return lines
# introduce some generic color names (idk how this will work, ig through parsing summaries? maybe? we'll figure that out ig)
color_names = {
"red":"0;31",
"green":"0;32",
"brown":"0;33",
"orange":"0;33",
"blue":"0;34",
"purple":"0;35",
"cyan":"0;36",
"yellow":"1;33",
"white":"1;37",
"blink":"5",
"bold":"1",
"italic":"3",
"underline":"4",
"inverse":"7",
"strikethrough":"9",
"light red":"1;31",
"light green":"1;32",
"light blue":"1;34",
"light purple":"1;36",
"light cyan":"0;37"
}
# add a function to colorize terminal text
def colorize(text,c):
if c in color_names:
return "\033["+color_names[c]+"m"+text+"\033[0m"
return "\033[38;2;"+str(c[0])+";"+str(c[1])+";"+str(c[2])+"m"+text+"\033[0m"
#calculate offset in months
def span(year,month,offset):
return year+((month+offset-1)//12),((month+offset-1)%12)+1
#get 2 date values - start value to scan from and end value to scan up to.
def getbounds(y,m,offset):
start = datetime.date(y,m,1)
postnextmonth = span(y,m,offset)
nextmonth = span(postnextmonth[0],postnextmonth[1],-1)
end = datetime.date(nextmonth[0],
nextmonth[1],
(datetime.date(postnextmonth[0],postnextmonth[1],1)-datetime.timedelta(days=1)).day)
return start,end
# generator for year/month pairs
def ympairs(startdate,offset,dstart = False):
if dstart == True:
startdate = span(startdate[0],startdate[1],-1)
for offset in range(offset):
yield span(startdate[0],startdate[1],offset)
return
# we calculate start/end dates and an offset for later use according to arguments
start = None
end = None
offset = None
if args["1"]:
start,end = getbounds(args["year"],args["month"],1)
offset = 1
elif args["3"]:
y,m = span(args["year"],args["month"],-1)
start,end = getbounds(y,m,3)
offset = 3
elif args["n"]:
start,end = getbounds(args["year"],args["month"],args["n"])
offset = args["n"]
else:
start,end = getbounds(args["year"],args["month"],1)
offset = 1
# connect to the DAV and receive calendars
client = caldav.DAVClient(url = config['DEFAULT']['uri'],
username = config['DEFAULT']['user'],
password = config['DEFAULT']['password'])
principal = client.principal()
calendars = principal.calendars()
# aggregate selected calendars
if "calendars" in config['DEFAULT']:
calendars2 = []
cals = config['DEFAULT']["calendars"].split(",")
for cal in calendars:
if cal.name in cal:
calendars2.append(cal)
calendars = calendars2
# hooo boy, fun things start here.
# we generate a dict of (year,month) pairs for easier indexing
events = {}
for ympair in ympairs((args['year'],args['month']),offset,dstart=args['3']):
events[ympair] = {}
# next, we iterate over events in each calendar, sorting them the folliwng way:
# events[(int year,int month)][int day] = [event event1, event event2, ...]
# looks awful, is awful, overall i love this.
for cal in calendars:
events_fetched = cal.date_search(start,end)
for event in events_fetched:
event = event.vobject_instance.vevent.contents
curdate = event["dtstart"][0].value
enddate = event["dtend"][0].value
while curdate <= enddate:
curdindex = (curdate.year,curdate.month)
if curdindex in events:
if not curdate.day in events[curdindex]:
events[curdindex][curdate.day] = []
events[curdindex][curdate.day].append(event)
else:
break
curdate += datetime.timedelta(days=1)
# if you're reading the source code for this (oof), feel free to suggest improvements for this, or, well, anything above or below this comment (as long as it's not just "rewrite this entire thing in C++ for me because i think python bad", "idk how but optimize stuff kthx". just don't be a dick, ok? thanks).
# and now we're just generating calendar lines
cal_prints = []
selected_date = datetime.date(args['year'],args['month'],args['day'])
for year,month in ympairs((args['year'],args['month']),offset,dstart=args['3']):
# a function to colorize cells in a more or less generic way
def lambdafunc(cell):
day = int(cell)
if day in events[(year,month)]:
event = events[(year,month)][day]
uid = event[0]["uid"][0].value.encode()
cell = colorize(cell,uid)
if datetime.date(year,month,day) == selected_date:
cell = colorize(cell,"inverse")
return cell
cal_prints.append(gencal(year,
month,
cell_modifier=lambdafunc))
# function to print 3 calendar lines at a time
def printlines(c1,c2,c3):
for count in range(len(c1)):
line = ""
line += c1.pop(0)+" "
if len(c2) > 0: line += c2.pop(0)+" "
if len(c3) > 0: line += c3.pop(0)+" "
print(line)
# array padding
for count in range(3-(len(cal_prints)%3)):
cal_prints.append([])
# finally, print the fucking calendar
for cal in range(0,len(cal_prints),3):
printlines(cal_prints[cal],cal_prints[cal+1],cal_prints[cal+2])