Browse Source

v0.1

master
Doug Le Tough 2 years ago
commit
0b4808aae8
8 changed files with 502 additions and 0 deletions
  1. +0
    -0
      README.md
  2. +95
    -0
      calendarmgr/__init__.py
  3. +47
    -0
      calterm.py
  4. +54
    -0
      cursesmgr/__init__.py
  5. +144
    -0
      displaymgr/__init__.py
  6. +106
    -0
      inputmgr/__init__.py
  7. +56
    -0
      logger/__init__.py
  8. BIN
      screenshot/calterm.png

+ 0
- 0
README.md View File


+ 95
- 0
calendarmgr/__init__.py View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import caldav
import inspect
import calendar
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}
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.calendar = None
self.caldav = None
self.scheme = scheme
self.url = url
self.user = user
self.passwd = passwd
self.init_calendar()
def init_calendar(self):
self.logger.info("[+] Initializing calendar")
self.calendar = calendar.Calendar()
def init_caldav(self):
try:
self.logger.info("[+] Initializing caldav")
url = "{}{}:{}@{}".format(self.scheme, self.user, self.passwd, self.url)
client = caldav.DAVClient(url)
principal = client.principal()
self.caldav = principal.calendars()
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))
for cal in self.caldav:
self.logger.info(" '-> %s [In collection: %s]" % (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
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def get_calendar_month_days(self, dtime=None):
if dtime is not None:
self.datetime = dtime
year, month = self.datetime.year, self.datetime.month
self.logger.info("[+] Getting calendar month: {:04}/{:02}".format(year, month))
cal = self.calendar.monthdayscalendar(year, month)
cal.insert(0, ["{:4}".format(day) for day in calendar.day_abbr])
return (self.datetime, cal)
def get_calendar_prevday(self):
new_date = self.datetime - relativedelta(days=1)
return self.get_calendar_month_days(new_date)
def get_calendar_nextday(self):
new_date = self.datetime + relativedelta(days=1)
return self.get_calendar_month_days(new_date)
def get_calendar_prevmonth_days(self):
new_date = self.datetime - relativedelta(months=1)
return self.get_calendar_month_days(new_date)
def get_calendar_nextmonth_days(self):
new_date = self.datetime + relativedelta(months=1)
return self.get_calendar_month_days(new_date)

+ 47
- 0
calterm.py View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import os
import locale
import curses
import inspect
import configparser
from logger import Logger
from cursesmgr import Cursesmgr
from displaymgr import Displaymgr
from calendarmgr import Calendarmgr
from inputmgr import Inputmgr
if __name__ == '__main__' :
logger = None
curses_manager = None
LOCAL_CONFIG_FILENAME = 'config.local.ini'
if not os.path.exists(LOCAL_CONFIG_FILENAME):
print("\033[91mError: Config file not found: %s\033[0m" % LOCAL_CONFIG_FILENAME)
exit(1)
config = configparser.ConfigParser()
try:
config.read(LOCAL_CONFIG_FILENAME)
locale.setlocale(locale.LC_TIME, config['global']['locale'])
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)
calendar_manager = Calendarmgr(config['calendar']['scheme'],
config['calendar']['url'],
config['calendar']['user'],
config['calendar']['passwd'],
config['calendar']['collection'],
config['global']['timezone'],
logger)
input_manager = Inputmgr(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))
if curses_manager:
curses_manager.end()
print("\033[91mError at startup: %s\033[0m" % str(e))
exit(1)

+ 54
- 0
cursesmgr/__init__.py View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import curses
import inspect
class Cursesmgr(object):
def __init__(self, logger):
self.height = None
self.width = None
self.stdscr = None
self.logger = logger
try:
self.init_screen()
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
self.end()
def init_screen(self):
self.logger.info(80*"-")
self.logger.info("[+] Screen initialization:")
self.stdscr = curses.initscr()
self.logger.info(" '-> Setting non blocking input mode")
self.stdscr.nodelay(True)
self.logger.info(" '-> Setting input max delay")
self.stdscr.timeout(1000)
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(" '-> Setting no echo mode")
curses.noecho()
self.logger.info(" '-> Hidding cursor")
curses.curs_set(False)
self.logger.info(" '-> Setting cbreak mode")
curses.cbreak()
self.logger.info(" '-> Setting keypad mode")
self.stdscr.keypad(True)
self.logger.info(80*"-")
def end(self):
self.logger.info(80*"-")
self.logger.info("[+] Restoring screen:")
self.logger.info(" '-> Restoring cursor")
curses.curs_set(True)
self.logger.info(" '-> Setting echo mode")
curses.echo()
self.logger.info(" '-> Setting cbreak mode off")
curses.nocbreak()
self.logger.info(" '-> Setting keypad mode off")
self.stdscr.keypad(False)
self.logger.info(" '-> Leaving curses mode")
curses.endwin()
self.logger.info(80*"-")

+ 144
- 0
displaymgr/__init__.py View File

@ -0,0 +1,144 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import curses
import inspect
from pytz import timezone
from datetime import datetime
class Displaymgr(object):
def __init__(self, cursesmgr, tz, logger):
self.height = int(curses.LINES)
self.width = int(curses.COLS)
self.cursesmgr = cursesmgr
self.tz = timezone(tz)
self.logger = logger
self.draw_calendar_win()
self.draw_help_win()
self.draw_todo_win()
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):
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)
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):
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)
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):
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)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def display_help(self):
try:
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")
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):
try:
self.calendar_win.erase()
self.calendar_win.box()
self.calendar_win.addstr(0, 1, " Calendar ")
day, cal = calendar
self.logger.info("[+] Drawing calendar view: %s" % 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)
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))
if day_index is not None:
self.calendar_win.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)
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):
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)
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:
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

+ 106
- 0
inputmgr/__init__.py View File

@ -0,0 +1,106 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import curses
import inspect
import curses.ascii
class Inputmgr(object):
def __init__(self, displaymgr, calendarmgr, logger):
self.displaymgr = displaymgr
self.calendarmgr = calendarmgr
self.running = False
self.logger = logger
self.displaymgr.draw_calendar(calendarmgr.get_calendar_month_days())
self.input_map = {
113: self.stop,
81: self.stop,
114: self.refresh,
82: self.refresh,
110: self.next,
78: self.next,
14: self.next,
112: self.prev,
80: self.prev,
16: self.prev,
97: self.add_event,
65: self.add_event,
101: self.edit_event,
69: self.edit_event,
100: self.delete_event,
68: self.delete_event,
116: self.add_todo,
84: self.add_todo,
109: self.mark_todo,
77: self.mark_todo,
107: self.delete_todo,
75: self.delete_todo
}
def run(self):
self.running = True
while self.running:
try:
self.char = self.displaymgr.help_win.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()
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()
def prev(self):
if curses.ascii.iscntrl(self.char):
return self.prevmonth()
return self.prevday()
def nextday(self):
self.logger.info("[+] Going to next day")
calendar = self.calendarmgr.get_calendar_nextday()
self.displaymgr.draw_calendar(calendar)
def prevday(self):
self.logger.info("[+] Going to previous day")
calendar = self.calendarmgr.get_calendar_prevday()
self.displaymgr.draw_calendar(calendar)
def nextmonth(self):
self.logger.info("[+] Going to next month")
calendar = self.calendarmgr.get_calendar_nextmonth_days()
self.displaymgr.draw_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)
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())
def add_event(self):
self.logger.info("[+] Adding event")
def edit_event(self):
self.logger.info("[+] Editing event")
def delete_event(self):
self.logger.info("[+] Deleting event")
def add_todo(self):
self.logger.info("[+] Adding todo")
def mark_todo(self):
self.logger.info("[+] Marking todo")
def delete_todo(self):
self.logger.info("[+] Delete todo")

+ 56
- 0
logger/__init__.py View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import os
import logging
colors = {'debug': '\033[97m',
'info': '\033[92m',
'warn': '\033[93m',
'error': '\033[95m',
'critical': '\033[91m'}
loglevels = {'debug': logging.DEBUG,
'info': logging.INFO,
'warn': logging.WARN,
'error': logging.ERROR,
'critical': logging.CRITICAL}
class Logger(object):
def __init__(self, loglevel, logfile):
self.logfile = logfile
if os.path.exists(self.logfile):
# Do not try to catch error, it will be trapped
# at higher level
os.remove(self.logfile)
self.filemode = 'a'
self.base_format = '%(levelname)-8s\033[0m - [%(asctime)s] %(message)s'
self.logformat = '%s%s' % (colors[loglevel], self.base_format)
self.logger = logging.getLogger('calterm')
self.logger.setLevel(loglevels[loglevel])
self.filehandler = logging.FileHandler(self.logfile)
self.filehandler.setLevel(loglevels[loglevel])
self.log_formatter = logging.Formatter(self.logformat, datefmt='%d/%b/%Y %H:%M:%S')
self.filehandler.setFormatter(self.log_formatter)
self.logger.addHandler(self.filehandler)
def set_logger(self, color):
self.logformat = '%s%s' % (color, self.base_format)
self.log_formatter = logging.Formatter(self.logformat, datefmt='%d/%b/%Y %H:%M:%S')
self.filehandler.setFormatter(self.log_formatter)
def debug(self, msg):
self.set_logger(colors['debug'])
self.logger.debug(msg)
def info(self, msg):
self.set_logger(colors['info'])
self.logger.info(msg)
def warn(self, msg):
self.set_logger(colors['warn'])
self.logger.warn(msg)
def error(self, msg):
self.set_logger(colors['error'])
self.logger.error(msg)
def critical(self, msg):
self.set_logger(colors['critical'])
self.logger.critical(msg)

BIN
screenshot/calterm.png View File

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

Loading…
Cancel
Save