Browse Source

v0.2

master
Doug Le Tough 2 years ago
parent
commit
cbe8cd4f4d
6 changed files with 256 additions and 131 deletions
  1. +88
    -25
      calendarmgr/__init__.py
  2. +3
    -3
      calterm.py
  3. +19
    -1
      cursesmgr/__init__.py
  4. +125
    -86
      displaymgr/__init__.py
  5. +21
    -16
      inputmgr/__init__.py
  6. BIN
      screenshot/calterm.png

+ 88
- 25
calendarmgr/__init__.py View File

@ -1,31 +1,52 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import re
import copy
import caldav
import inspect
import calendar
from icalendar import Event
from pytz import timezone
from datetime import datetime
from caldav.elements import dav, cdav
from dateutil.relativedelta import relativedelta
class Calendarmgr(object):
event = {'DTSTAMP': None,
'UID': None,
'SUMMARY': None,
'DESCRIPTION': None,
'LOCATION': None,
'DTSTART': None,
'DTEND': None,
'TRIGGER': None}
event = {
'VCALENDAR': {
'VERSION': None,
'PRODID': None,
'CALSCALE': None,
},
'VEVENT': {
'DTSAMP': None,
'UID': None,
'SEQUENCE': None,
'SUMMARY': None,
'DTSTART': None,
'DTEND': None,
'STATUS': None,
'ORGANIZER': None,
'LOCATION': None,
'DESCRIPTION': None,
},
'VALARM': {
'TRIGGER': None,
'ACTION': None,
'DESCRIPTION': None,
'DTSTAMP': None,
}
}
def __init__(self, scheme, url, user, passwd, collection, tz, logger):
self.logger = logger
self.collection = collection
self.tz = timezone(tz)
self.datetime = self.tz.localize(datetime.now())
self.datetime = datetime.now(timezone('UTC')).replace(hour=0, minute=0,second=0, microsecond=0)
self.calendar = None
self.caldav = None
self.todos = []
self.scheme = scheme
self.url = url
self.user = user
@ -46,26 +67,68 @@ class Calendarmgr(object):
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def str_multireplace(self, string , replacements):
substrs = sorted(replacements, key=len, reverse=True)
regexp = re.compile('|'.join(map(re.escape, substrs)))
return regexp.sub(lambda match: replacements[match.group(0)], string)
def event_as_dict(self, event):
try:
section = None
header = None
patterns = {"\\n": " ", "\\": ""}
dict_event = copy.deepcopy(self.event)
for line in event.data.split("\r\n"):
if len(line):
try:
if line.startswith("BEGIN:"):
section = line[line.index(":")+1:]
elif line.startswith(" "):
dict_event[section][header] += self.str_multireplace(line[1:], patterns)
else:
# Do not split on ":" because line may have several ":"
header = line[:line.index(":")]
dict_event[section][header] = self.str_multireplace(line[line.index(":")+1:], patterns)
except ValueError as e:
if line.startswith("ATTENDEE"):
dict_event[section][header] += " {}".format(self.str_multireplace(line, patterns))
continue
raise(Exception("WTFError: CalDav is shit: {}".format(str(e))))
self.logger.debug(dict_event)
return copy.deepcopy(dict_event)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def get_events(self):
try:
events = []
start = self.datetime.astimezone(timezone('UTC')) - relativedelta(days=1)
end = self.datetime.astimezone(timezone('UTC'))
self.logger.info("[+] Retrieving event from caldav: [%s -> %s]" % (start, end))
formated_events = []
start = self.datetime.replace(tzinfo=self.tz).astimezone(timezone('UTC'))
end = start + relativedelta(days=1)
self.logger.info("[+] Retrieving events from caldav: [%s -> %s]" % (start, end))
for cal in self.caldav:
self.logger.info(" '-> %s [In collection: %s]" % (cal.name, cal.name in self.collection))
self.logger.info(" '-> {} [In collection: {}]".format(cal.name, cal.name in self.collection))
if cal.name in self.collection:
results = cal.date_search(start, end)
for event in results:
self.logger.info(" '-> %s" % str(event).split('/')[-1])
eevent = dict(self.event)
e = event.data.split('\r\n')
for line in e:
key = line.split(':')[0]
if key in eevent:
eevent[key] = line[line.index(':')+1:]
events.append(eevent)
return events
events = cal.date_search(start, end)
for event in events:
self.logger.info(" '--> {}".format(str(event).split('/')[-1]))
formated_events.append(self.event_as_dict(event))
self.get_todos(cal)
return formated_events
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def get_todos(self, cal):
try:
self.logger.info(" [+] Retrieving todos from cal: {}".format(cal.name))
for todo in cal.todos():
self.logger.info(" '-> {}".format(str(todo).split('/')[-1]))
lines = todo.data.split("\n")
for line in lines:
key = line.split(':', 1)[0]
if key == 'SUMMARY':
value = line.split(':', 1)[1]
if value not in self.todos:
self.todos.append(value)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))


+ 3
- 3
calterm.py View File

@ -27,7 +27,7 @@ if __name__ == '__main__' :
logger = Logger(config['logger']['level'], config['logger']['logfile'])
logger.info("Starting application...")
curses_manager = Cursesmgr(logger)
display_manager = Displaymgr(curses_manager, config['global']['timezone'], logger)
display_manager = Displaymgr(config['global']['timezone'], logger)
calendar_manager = Calendarmgr(config['calendar']['scheme'],
config['calendar']['url'],
config['calendar']['user'],
@ -35,12 +35,12 @@ if __name__ == '__main__' :
config['calendar']['collection'],
config['global']['timezone'],
logger)
input_manager = Inputmgr(display_manager, calendar_manager, logger)
input_manager = Inputmgr(curses_manager, display_manager, calendar_manager, logger)
input_manager.run()
logger.info('[+] Quitting')
except Exception as e:
if logger:
logger.critical('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
logger.critical('[%s %s()]: %s "%s"' % ('calterm.py', inspect.currentframe().f_code.co_name, e.__class__, e))
if curses_manager:
curses_manager.end()
print("\033[91mError at startup: %s\033[0m" % str(e))


+ 19
- 1
cursesmgr/__init__.py View File

@ -12,6 +12,7 @@ class Cursesmgr(object):
self.logger = logger
try:
self.init_screen()
self.init_colors()
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
self.end()
@ -20,6 +21,7 @@ class Cursesmgr(object):
self.logger.info(80*"-")
self.logger.info("[+] Screen initialization:")
self.stdscr = curses.initscr()
self.init_colors()
self.logger.info(" '-> Setting non blocking input mode")
self.stdscr.nodelay(True)
self.logger.info(" '-> Setting input max delay")
@ -27,7 +29,7 @@ class Cursesmgr(object):
self.logger.info(" '-> Geting terminal size")
self.height = int(curses.LINES)
self.width = int(curses.COLS)
self.logger.info(" '-> H: %d / W: %d" % (self.height, self.width))
self.logger.info(" |'-> H: %d / W: %d" % (self.height, self.width))
self.logger.info(" '-> Setting no echo mode")
curses.noecho()
self.logger.info(" '-> Hidding cursor")
@ -38,6 +40,22 @@ class Cursesmgr(object):
self.stdscr.keypad(True)
self.logger.info(80*"-")
def init_colors(self):
self.logger.info("[+] Defining colors")
curses.start_color()
curses.use_default_colors()
# Dark BG color
curses.init_color(1, 134, 176, 196)
# Light BG color
curses.init_color(2, 161, 208, 231)
# Edit BG color
curses.init_color(3, 184, 239, 267)
# Text color
curses.init_color(4, 659, 702, 718)
curses.init_pair(1, 4, 1)
curses.init_pair(2, 4, 2)
curses.init_pair(3, 4, 3)
def end(self):
self.logger.info(80*"-")
self.logger.info("[+] Restoring screen:")


+ 125
- 86
displaymgr/__init__.py View File

@ -7,138 +7,177 @@ from pytz import timezone
from datetime import datetime
class Displaymgr(object):
def __init__(self, cursesmgr, tz, logger):
def __init__(self, tz, logger):
self.fetch_msg = "Fetching data..."
self.height = int(curses.LINES)
self.width = int(curses.COLS)
self.cursesmgr = cursesmgr
self.windows = {}
self.calendar_width = self.width - 30
self.calendar_height = self.height - 7
self.events_x = 42
self.events_width = self.calendar_width - self.events_x - 1
self.events_height = self.calendar_height - 2
self.tz = timezone(tz)
self.logger = logger
self.draw_calendar_win()
self.draw_help_win()
self.draw_todo_win()
self.create_window("calendar", 0, 0, self.calendar_width, self.calendar_height, parent=None)
self.create_window("todo", self.calendar_width, 0, self.width - self.calendar_width, self.calendar_height, parent=None)
self.create_window("help", 0, self.calendar_height, self.width, self.height - self.calendar_height, parent=None)
self.create_window("events", self.events_x, 1, self.events_width, self.events_height, parent="calendar")
self.display_help()
self.refresh_screen()
def refresh_screen(self):
self.logger.info("[+] Screen refresh")
curses.doupdate()
def refresh_win(self, win):
self.logger.info("[+] Refreshing window: %s" % win)
win.noutrefresh()
self.refresh_screen()
def draw_calendar_win(self):
def create_window(self, name, x, y, width, height, parent=None):
try:
self.logger.info("[+] Drawing calendar window")
self.calendar_win = curses.newwin(self.height - 7, self.width - 30, 0, 0)
self.calendar_win.box()
self.calendar_win.addstr(0, 1, " Calendar ")
self.refresh_win(self.calendar_win)
self.logger.info("[+] Creating window: {} [{}, {}, {}, {}]".format(name, x, y, width, height))
if not parent:
window = curses.newwin(height, width, y, x)
window.bkgdset(curses.color_pair(1))
window.box()
else:
parent = self.windows[parent][0]
window = parent.derwin(height, width, y, x)
window.bkgdset(curses.color_pair(3))
window.addstr(0, 1, " {} ".format(name.title()))
self.add_window(window, name, parent)
except KeyError as e:
self.logger.error("[{} {}] No suck window: {}".format(self.__class__, inspect.currentframe().f_code.co_name, name))
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def draw_todo_win(self):
def clear_window(self, name):
try:
self.logger.info("[+] Drawing todo window")
self.todo_win = curses.newwin(self.height - 7, 30, 0, self.width - 30)
self.todo_win.box()
self.todo_win.addstr(0, 1, " Todo ")
self.refresh_win(self.todo_win)
self.logger.info("[+] Clearing window: {}".format(name))
self.windows[name][0].erase()
if not self.windows[name][1]:
self.windows[name][0].box()
self.windows[name][0].addstr(0, 1, " {} ".format(name.title()))
except KeyError as e:
self.logger.error("No suck window: {}".format(name))
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def draw_help_win(self):
def add_window(self, window, name, wtype):
try:
self.logger.info("[+] Drawing help window")
self.help_win = curses.newwin(7, self.width, self.height - 7, 0)
self.help_win.box()
self.help_win.addstr(0, 1, " Help ")
self.display_help()
self.refresh_win(self.help_win)
self.logger.info("[+] Adding window: {}".format(name))
self.windows[name] = (window, wtype)
self.clear_window(name)
self.refresh_window(name)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def refresh_window(self, window_name):
try:
self.logger.info("[+] Refreshing window: {}".format(window_name))
self.windows[window_name][0].noutrefresh()
self.refresh_screen()
except KeyError as e:
self.logger.error("[{} {}] No suck window: {}".format(self.__class__, inspect.currentframe().f_code.co_name, name))
def refresh_screen(self):
self.logger.info("[+] Screen refresh")
curses.doupdate()
def format_event(self, event, max_width):
self.logger.info("[+] formating event")
lines = []
for header in event:
lline = []
for line in header.split("\n"):
for word in line.split():
lline.append(word)
if max_width < len(" ".join(lline)):
lline.pop()
lines.append(" ".join(lline))
lline = [word]
continue
lines.append(" ".join(lline))
return lines
def display_help(self):
try:
self.clear_window("help")
self.logger.info("[+] Displaying help")
self.help_win.addstr(2, 2, "Q: Quit\t\tR: Refresh\t\tP: Prev. day\t\tN: Next day\t\t^P: Prev. month\t\t^N: Next month")
self.help_win.addstr(3, 2, "A: Add event\t\tE: Edit event\t\tD: Delete event")
self.help_win.addstr(4, 2, "T: Add Todo\t\tM: Mark todo\t\tK: Delete Todo")
self.windows["help"][0].addstr(2, 2, "Q: Quit\t\tR: Refresh\t\tP: Prev. day\t\tN: Next day\t\t^P: Prev. month\t\t^N: Next month")
self.windows["help"][0].addstr(3, 2, "A: Add event\t\tE: Edit event\t\tD: Delete event")
self.windows["help"][0].addstr(4, 2, "T: Add Todo\t\tM: Mark todo\t\tK: Delete Todo")
self.refresh_window("help")
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def draw_calendar(self, calendar):
def display_calendar(self, calendar):
try:
self.calendar_win.erase()
self.calendar_win.box()
self.calendar_win.addstr(0, 1, " Calendar ")
self.clear_window("calendar")
day, cal = calendar
self.logger.info("[+] Drawing calendar view: %s" % day)
self.logger.info("[+] Displaying calendar view: {}".format(day))
dday, month, year = day.day, day.month, day.year
month = '{:02}'.format(month)
month_year = "%s/%s" % (month, year)
dows = " ".join(cal[0])
self.calendar_win.addstr(2, int(len(dows)/2-len(month_year)/2) + 2, month_year)
self.calendar_win.addstr(3, 1, dows)
self.windows["calendar"][0].addstr(2, int(len(dows)/2-len(month_year)/2) + 2, month_year)
self.windows["calendar"][0].addstr(3, 1, dows)
line_num = 5
day_index = None
for days in cal[1:]:
if dday in days:
day_index = days.index(dday)
week = ['{:02}'.format(ddday) if ddday > 0 else ' ' for ddday in days]
self.calendar_win.addstr(line_num, 2, " ".join(week))
self.windows["calendar"][0].addstr(line_num, 2, " ".join(week))
if day_index is not None:
self.calendar_win.addstr(line_num, 2+6*day_index,'{:02}'.format(dday), curses.A_REVERSE)
self.windows["calendar"][0].addstr(line_num, 2+6*day_index,'{:02}'.format(dday), curses.A_REVERSE)
day_index = None
line_num += 2
#~ week = " ".join([' ' for dday in range(0, 7)])
#~ self.calendar_win.addstr(line_num, 2, week)
self.draw_intraday(day, len(week)*5)
self.refresh_win(self.calendar_win)
self.display_today(day)
self.refresh_window("calendar")
self.display_todos(None)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def draw_intraday(self, day, cal_width):
def display_today(self, day):
try:
today = '{:04}/{:02}/{:02}'.format(day.year, day.month, day.day,)
self.logger.info("[+] Drawing intraday view: %s" % today)
max_height, max_width = self.calendar_win.getmaxyx()
#~ self.intraday_win = None
self.intraday_win = self.calendar_win.subwin(max_height - 2, max_width - cal_width - 8, 1, cal_width + 6)
#~ self.intraday_win.erase()
max_height, max_width = self.intraday_win.getmaxyx()
self.intraday_win.addstr(1, int(max_width/2)-int(len(today)/2)+2, today)
self.intraday_win.vline(2, 1, '|', max_height - 3)
self.intraday_win.vline(2, max_width - 2, '|', max_height - 3)
self.intraday_win.addch(2, 1, curses.ACS_ULCORNER)
self.intraday_win.addch(2, max_width - 2, curses.ACS_URCORNER)
self.intraday_win.addch(max_height - 1, 1, curses.ACS_LLCORNER)
self.intraday_win.addch(max_height - 1, max_width - 2, curses.ACS_LRCORNER)
self.refresh_win(self.intraday_win)
today = '{:04}/{:02}/{:02}'.format(day.year, day.month, day.day)
self.logger.info("[+] Displaying today events: %s" % today)
self.clear_window('events')
max_height, max_width = self.windows['events'][0].getmaxyx()
self.windows["events"][0].addstr(1, int(max_width/2)-int(len(today)/2)+2, "{}".format(today))
self.windows['events'][0].hline(2, 2, curses.ACS_HLINE, max_width-4)
self.refresh_window("events")
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def display_events(self, events):
self.logger.info("[+] Displaying events")
max_height, max_width = self.intraday_win.getmaxyx()
eevents = sorted(events, key=lambda k: k['DTSTART'])
index = 1
for event in eevents:
max_height, max_width = self.windows['events'][0].getmaxyx()
empty_msg = len(self.fetch_msg) * " "
events = sorted(events, key=lambda k: k['VEVENT']['DTSTART'])
v_offset = 3
for event in events:
tz = timezone('UTC')
start = tz.localize(datetime.strptime(event['DTSTART'], '%Y%m%dT%H%M%SZ')).astimezone(self.tz)
start = datetime.strftime(start, '%Y/%m/%d %H:%M')
end = tz.localize(datetime.strptime(event['DTEND'], '%Y%m%dT%H%M%SZ')).astimezone(self.tz)
end = datetime.strftime(end, '%Y/%m/%d %H:%M')
location = event['LOCATION']
summary = event['SUMMARY']
descr = event['DESCRIPTION']
trigger = event['TRIGGER']
eevent = "{} -> {}".format(start, end)
eeevent = "Location: {}".format(location)
eeeevent = "Summary: {}".format(summary)
self.intraday_win.addstr(4*index-1, 2, eevent)
self.intraday_win.addstr(4*index, 2, eeevent)
self.intraday_win.addstr(4*index+1, 2, eeeevent)
self.intraday_win.hline(4*index+2, 2, '-', max_width-4)
self.refresh_win(self.intraday_win)
index += 1
start = tz.localize(datetime.strptime(event['VEVENT']['DTSTART'], '%Y%m%dT%H%M%SZ')).astimezone(self.tz)
start = datetime.strftime(start, '%H:%M')
end = tz.localize(datetime.strptime(event['VEVENT']['DTEND'], '%Y%m%dT%H%M%SZ')).astimezone(self.tz)
end = datetime.strftime(end, '%H:%M')
date = "{} -> {} [{}]".format(start, end, event['VALARM']['TRIGGER'])
location = "Location: {}".format(event['VEVENT']['LOCATION'])
summary = "Summary: {}".format(event['VEVENT']['SUMMARY'])
description = "Description: {}".format(event['VEVENT']['DESCRIPTION'])
event = self.format_event([date, location, summary, description], max_width-4)
for line in event:
self.windows['events'][0].addstr(v_offset, 2, line)
v_offset += 1
self.windows['events'][0].hline(v_offset, 2, curses.ACS_HLINE, max_width-4)
v_offset += 1
self.refresh_window('events')
def display_todos(self, todos):
self.logger.info("[+] Displaying todos")
self.clear_window("todo")
max_height, max_width = self.windows['todo'][0].getmaxyx()
index = 0
if todos:
for todo in todos:
if len(todo) > max_width - 6:
todo = todo[0:max_width - 6]
self.windows['todo'][0].addch(index + 2, 2, curses.ACS_BULLET)
self.windows['todo'][0].addstr(index + 2, 4, todo)
index += 1
self.refresh_window('todo')

+ 21
- 16
inputmgr/__init__.py View File

@ -6,12 +6,14 @@ import inspect
import curses.ascii
class Inputmgr(object):
def __init__(self, displaymgr, calendarmgr, logger):
def __init__(self, cursesmgr, displaymgr, calendarmgr, logger):
self.cursesmgr = cursesmgr
self.displaymgr = displaymgr
self.calendarmgr = calendarmgr
self.running = False
self.logger = logger
self.displaymgr.draw_calendar(calendarmgr.get_calendar_month_days())
self.displaymgr.display_calendar(calendarmgr.get_calendar_month_days())
self.refresh()
self.input_map = {
113: self.stop,
81: self.stop,
@ -41,56 +43,59 @@ class Inputmgr(object):
self.running = True
while self.running:
try:
self.char = self.displaymgr.help_win.getch()
self.char = self.displaymgr.windows["help"][0].getch()
self.logger.debug("Input: %s [%s] / Ctrl: %s" % (curses.unctrl(self.char), self.char, curses.ascii.iscntrl(self.char)))
self.input_map[self.char]()
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
self.displaymgr.cursesmgr.end()
self.cursesmgr.end()
def stop(self):
self.logger.info("[+] Stopping input manager")
self.running = False
def refresh(self):
self.logger.info("[+] Refreshing calendar")
def next(self):
if curses.ascii.iscntrl(self.char):
return self.nextmonth()
return self.nextday()
self.nextmonth()
self.refresh()
return
self.nextday()
self.refresh()
def prev(self):
if curses.ascii.iscntrl(self.char):
return self.prevmonth()
return self.prevday()
self.prevmonth()
self.refresh()
return
self.prevday()
self.refresh()
def nextday(self):
self.logger.info("[+] Going to next day")
calendar = self.calendarmgr.get_calendar_nextday()
self.displaymgr.draw_calendar(calendar)
self.displaymgr.display_calendar(calendar)
def prevday(self):
self.logger.info("[+] Going to previous day")
calendar = self.calendarmgr.get_calendar_prevday()
self.displaymgr.draw_calendar(calendar)
self.displaymgr.display_calendar(calendar)
def nextmonth(self):
self.logger.info("[+] Going to next month")
calendar = self.calendarmgr.get_calendar_nextmonth_days()
self.displaymgr.draw_calendar(calendar)
self.displaymgr.display_calendar(calendar)
def prevmonth(self):
self.logger.info("[+] Going to previous month")
calendar = self.calendarmgr.get_calendar_prevmonth_days()
day = self.calendarmgr.datetime
self.displaymgr.draw_calendar(calendar)
self.displaymgr.display_calendar(calendar)
def refresh(self):
self.logger.info("[+] Refreshing calendar")
if not self.calendarmgr.caldav:
self.calendarmgr.init_caldav()
self.displaymgr.display_events(self.calendarmgr.get_events())
self.displaymgr.display_todos(self.calendarmgr.todos)
def add_event(self):
self.logger.info("[+] Adding event")


BIN
screenshot/calterm.png View File

Before After
Width: 1440  |  Height: 900  |  Size: 553 KiB Width: 1440  |  Height: 862  |  Size: 22 KiB

Loading…
Cancel
Save