ya3

ya3 -- yet another appointment application
Log | Files | Refs

commit dcc8d3d2b18ecb3eea0ec734397981567136a6dd
parent cf2cc3fdc40c4173eee00bfa981f3c9da7c1abda
Author: Matthias Balk <matthias.balk@fotopuzzle.de>
Date:   Wed,  8 May 2019 14:23:57 +0200

use virtualenv

Diffstat:
M.gitignore | 1+
AMakefile | 3+++
MREADME.rst | 5++---
Dcommands.py | 120-------------------------------------------------------------------------------
Dconfig.py | 12------------
Arequirements-freeze.txt | 3+++
Arequirements.txt | 2++
Asrc/commands.py | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config.py | 12++++++++++++
Asrc/ya3 | 45+++++++++++++++++++++++++++++++++++++++++++++
Mya3 | 32++++----------------------------
11 files changed, 192 insertions(+), 163 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ __pycache__ tags +env diff --git a/Makefile b/Makefile @@ -0,0 +1,3 @@ +setup: + virtualenv -p python3 env + . env/bin/activate && pip install -r requirements-freeze.txt diff --git a/README.rst b/README.rst @@ -21,8 +21,7 @@ Features to be implemented Requirements ~~~~~~~~~~~~ - Python3 -- Python dateutil -- Python vobject +- virtualenv Installation and setup ~~~~~~~~~~~~~~~~~~~~~~ @@ -30,7 +29,7 @@ Get ya3 and place it where you like: $ cd /path/where/ya3/shall/be/put $ git pull .... ya3 -Edit configuration in config.py to fit your needs. +Edit configuration in src/config.py to fit your needs. Usage ~~~~~ diff --git a/commands.py b/commands.py @@ -1,120 +0,0 @@ -# Copyright 2018 Matthias Balk -# -# This file is part of ya3 (yet another appointment application). -# -# ya3 is free software: you can redistribute it and/or modify it under the -# terms of the GNU Affero General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# ya3 is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with ya3. If not, see <http://www.gnu.org/licenses/>. - -import os -import re -import time -import uuid -from collections import OrderedDict -from datetime import datetime - -import vobject -from dateutil.parser import parse -from dateutil.tz import gettz - -import config - - -def _localtime2utc(datetimestr): - local = parse('%s %s' % (datetimestr, time.tzname[0])) - return local - local.utcoffset() - - -def _get_input(key): - if key in config.DEFAULTS: - i = input('%s: [%s] ' % (key, config.DEFAULTS[key])) - return i if i.strip() is not '' else config.DEFAULTS[key] - return input('%s: ' % key) - - -def _event2file(event): - filename = '%s-%s.ics' % (event['DTSTART'].strftime('%Y-%m-%d'), - re.sub(r'\s+', '-', event['SUMMARY'].lower())) - f = open(os.path.join(config.CAL_DIR, filename), 'w') - f.write('BEGIN:VCALENDAR\nVERSION:2.0\n') - f.write('PRODID:-//ya3//yet another appointment application//\n') - - def item2str(key, value): - if isinstance(value, datetime): - return '%s:%s\n' % (key, value.strftime('%Y%m%dT%H%M%SZ')) - return '%s:%s\n' % (key, value) - - for key in event: - f.write(item2str(key, event[key])) - - f.write('END:VCALENDAR\n') - f.close() - - -def create_event(): - event = OrderedDict([ - ('BEGIN', 'VEVENT'), - ('DTSTART', None), - ('DTEND', None), - ('LOCATION', None), - ('SUMMARY', None), - ('UID', str(uuid.uuid4())), - ('END', 'VEVENT'), - ]) - - for key in ['DTSTART', 'DTEND', 'LOCATION', 'SUMMARY']: - event[key] = _get_input(key) - if re.match('DT', key): - event[key] = _localtime2utc(event[key]) - - _event2file(event) - - -def _conv2local(dt): - if not isinstance(dt, datetime): - return datetime(dt.year, dt.month, dt.day, tzinfo=gettz()) - if dt.tzinfo is None: - return datetime(dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second, - tzinfo=gettz()) - return dt.astimezone(gettz()) - - -def print_events(icsfilenames): - - def _is_in_range(event): - start = _conv2local(event.dtstart.value) - end = _conv2local(event.dtend.value) - return (end >= datetime.now(gettz()) - config.MAX_AGE and - start < datetime.now(gettz()) + config.MAX_AHEAD) - - def _print(event): - start = _conv2local(event.dtstart.value) - end = _conv2local(event.dtend.value) - - try: - location = " (" + event.location.value + ")" - except AttributeError: - location = "" - - print('%s - %s %s%s' % (start.strftime('%Y-%m-%d %H:%M'), - end.strftime('%H:%M'), - event.summary.value, - location)) - - events = [] - for filename in icsfilenames: - with open(filename) as f: - for cal in vobject.readComponents(f): - events.extend(filter(_is_in_range, cal.vevent_list)) - for event in sorted(events, key=lambda ev: _conv2local(ev.dtstart.value)): - _print(event) diff --git a/config.py b/config.py @@ -1,12 +0,0 @@ -from datetime import timedelta -from os import path - -CAL_DIR = path.expanduser('~/.ya3/calendars/') - -DEFAULTS = { - 'LOCATION': 'Besprechungsraum', - 'SUMMARY': 'Meeting' -} - -MAX_AGE = timedelta(days=1) -MAX_AHEAD = timedelta(days=14) diff --git a/requirements-freeze.txt b/requirements-freeze.txt @@ -0,0 +1,3 @@ +python-dateutil==2.8.0 +six==1.12.0 +vobject==0.9.6.1 diff --git a/requirements.txt b/requirements.txt @@ -0,0 +1,2 @@ +python-dateutil +vobject diff --git a/src/commands.py b/src/commands.py @@ -0,0 +1,120 @@ +# Copyright 2018, 2019 Matthias Balk +# +# This file is part of ya3 (yet another appointment application). +# +# ya3 is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# ya3 is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with ya3. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import time +import uuid +from collections import OrderedDict +from datetime import datetime + +import vobject +from dateutil.parser import parse +from dateutil.tz import gettz + +import config + + +def _localtime2utc(datetimestr): + local = parse('%s %s' % (datetimestr, time.tzname[0])) + return local - local.utcoffset() + + +def _get_input(key): + if key in config.DEFAULTS: + i = input('%s: [%s] ' % (key, config.DEFAULTS[key])) + return i if i.strip() is not '' else config.DEFAULTS[key] + return input('%s: ' % key) + + +def _event2file(event): + filename = '%s-%s.ics' % (event['DTSTART'].strftime('%Y-%m-%d'), + re.sub(r'\s+', '-', event['SUMMARY'].lower())) + f = open(os.path.join(config.CAL_DIR, filename), 'w') + f.write('BEGIN:VCALENDAR\nVERSION:2.0\n') + f.write('PRODID:-//ya3//yet another appointment application//\n') + + def item2str(key, value): + if isinstance(value, datetime): + return '%s:%s\n' % (key, value.strftime('%Y%m%dT%H%M%SZ')) + return '%s:%s\n' % (key, value) + + for key in event: + f.write(item2str(key, event[key])) + + f.write('END:VCALENDAR\n') + f.close() + + +def create_event(): + event = OrderedDict([ + ('BEGIN', 'VEVENT'), + ('DTSTART', None), + ('DTEND', None), + ('LOCATION', None), + ('SUMMARY', None), + ('UID', str(uuid.uuid4())), + ('END', 'VEVENT'), + ]) + + for key in ['DTSTART', 'DTEND', 'LOCATION', 'SUMMARY']: + event[key] = _get_input(key) + if re.match('DT', key): + event[key] = _localtime2utc(event[key]) + + _event2file(event) + + +def _conv2local(dt): + if not isinstance(dt, datetime): + return datetime(dt.year, dt.month, dt.day, tzinfo=gettz()) + if dt.tzinfo is None: + return datetime(dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + tzinfo=gettz()) + return dt.astimezone(gettz()) + + +def print_events(icsfilenames): + + def _is_in_range(event): + start = _conv2local(event.dtstart.value) + end = _conv2local(event.dtend.value) + return (end >= datetime.now(gettz()) - config.MAX_AGE and + start < datetime.now(gettz()) + config.MAX_AHEAD) + + def _print(event): + start = _conv2local(event.dtstart.value) + end = _conv2local(event.dtend.value) + + try: + location = " (" + event.location.value + ")" + except AttributeError: + location = "" + + print('%s - %s %s%s' % (start.strftime('%Y-%m-%d %H:%M'), + end.strftime('%H:%M'), + event.summary.value, + location)) + + events = [] + for filename in icsfilenames: + with open(filename) as f: + for cal in vobject.readComponents(f): + events.extend(filter(_is_in_range, cal.vevent_list)) + for event in sorted(events, key=lambda ev: _conv2local(ev.dtstart.value)): + _print(event) diff --git a/src/config.py b/src/config.py @@ -0,0 +1,12 @@ +from datetime import timedelta +from os import path + +CAL_DIR = path.expanduser('~/.ya3/') + +DEFAULTS = { + 'LOCATION': 'Besprechungsraum I', + 'SUMMARY': 'Meeting' +} + +MAX_AGE = timedelta(hours=6) +MAX_AHEAD = timedelta(days=14) diff --git a/src/ya3 b/src/ya3 @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# Copyright 2018, 2019 Matthias Balk +# +# This file is part of ya3 (yet another appointment application). +# +# ya3 is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# ya3 is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with ya3. If not, see <http://www.gnu.org/licenses/>. + +"""ya3 -- yet another appointment application""" + +import glob +import os.path +from optparse import OptionParser + +import commands +import config + + +usage = """%prog <command> + commands: create|add, print""" +parser = OptionParser(usage=usage) +(options, args) = parser.parse_args() + +if len(args) != 1: + parser.print_help() +else: + command = args[0] + + if command == 'create' or command == 'add': + commands.create_event() + elif command == 'print': + commands.print_events(glob.glob(os.path.join(config.CAL_DIR, '*.ics'))) + else: + parser.print_help() diff --git a/ya3 b/ya3 @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/bin/sh -# Copyright 2018 Matthias Balk +# Copyright 2018, 2019 Matthias Balk # # This file is part of ya3 (yet another appointment application). # @@ -17,29 +17,5 @@ # You should have received a copy of the GNU Affero General Public License # along with ya3. If not, see <http://www.gnu.org/licenses/>. -"""ya3 -- yet another appointment application""" - -import glob -import os.path -from optparse import OptionParser - -import commands -import config - - -usage = """%prog <command> - commands: create|add, print""" -parser = OptionParser(usage=usage) -(options, args) = parser.parse_args() - -if len(args) != 1: - parser.print_help() -else: - command = args[0] - - if command == 'create' or command == 'add': - commands.create_event() - elif command == 'print': - commands.print_events(glob.glob(os.path.join(config.CAL_DIR, '*.ics'))) - else: - parser.print_help() +. $(dirname `readlink -f $0`)/env/bin/activate +$(dirname `readlink -f $0`)/src/ya3 $@