diff --git a/scripts/boobcoming b/scripts/boobcoming new file mode 100755 index 0000000000000000000000000000000000000000..a6d77a653b8feffa34e1525e4c1117829eed9206 --- /dev/null +++ b/scripts/boobcoming @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Bezleputh +# +# This file is part of weboob. +# +# weboob 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. +# +# weboob 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 weboob. If not, see . + + +from weboob.applications.boobcoming import Boobcoming + +if __name__ == '__main__': + Boobcoming.run() diff --git a/weboob/applications/boobcoming/__init__.py b/weboob/applications/boobcoming/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e817eef299e2b62b03b951e346531d06065730e9 --- /dev/null +++ b/weboob/applications/boobcoming/__init__.py @@ -0,0 +1,23 @@ + +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Bezleputh +# +# This file is part of weboob. +# +# weboob 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. +# +# weboob 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 weboob. If not, see . + +from .boobcoming import Boobcoming + +__all__ = ['Boobcoming'] diff --git a/weboob/applications/boobcoming/boobcoming.py b/weboob/applications/boobcoming/boobcoming.py new file mode 100644 index 0000000000000000000000000000000000000000..9f1c1efffb7da7d5531cefc65b6f8b3dae1e274b --- /dev/null +++ b/weboob/applications/boobcoming/boobcoming.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Bezleputh +# +# This file is part of weboob. +# +# weboob 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. +# +# weboob 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 weboob. If not, see . + +import re +import sys +from datetime import date, timedelta, time, datetime + +from weboob.tools.application.formatters.iformatter import IFormatter, PrettyFormatter +from weboob.capabilities.base import empty +from weboob.capabilities.calendar import ICapCalendarEvent +from weboob.tools.application.repl import ReplApplication, defaultcount + +__all__ = ['Boobcoming'] + + +class ICalFormatter(IFormatter): + MANDATORY_FIELDS = ('id', 'start_date', 'end_date', 'summary') + + def start_format(self, **kwargs): + self.output(u'BEGIN:VCALENDAR') + self.output(u'VERSION:2.0') + self.output(u'PRODID:-//hacksw/handcal//NONSGML v1.0//EN') + + def format_obj(self, obj, alias): + result = u'BEGIN:VEVENT\n' + result += u'DTSTART:%s\n' % obj.start_date.strftime("%Y%m%dT%H%M%SZ") + result += u'DTEND:%s\n' % obj.end_date.strftime("%Y%m%dT%H%M%SZ") + result += u'SUMMARY:%s\n' % obj.summary + if hasattr(obj, 'location') and not empty(obj.location): + result += u'LOCATION:%s\n' % obj.location + + if hasattr(obj, 'categories') and not empty(obj.categories): + result += u'CATEGORIES:%s\n' % obj.categories + + if hasattr(obj, 'status') and not empty(obj.status): + result += u'STATUS:%s\n' % obj.status + + if hasattr(obj, 'description') and not empty(obj.description): + result += u'DESCRIPTION:%s\n' % obj.description.replace('\r\n', '\\n') + + if hasattr(obj, 'transp') and not empty(obj.transp): + result += u'TRANSP:%s\n' % obj.transp + + if hasattr(obj, 'sequence') and not empty(obj.sequence): + result += u'SEQUENCE:%s\n' % obj.sequence + + if hasattr(obj, 'url') and not empty(obj.url): + result += u'URL:%s\n' % obj.url + + result += u'END:VEVENT' + return result + + def flush(self, **kwargs): + self.output(u'END:VCALENDAR') + + +class UpcomingListFormatter(PrettyFormatter): + MANDATORY_FIELDS = ('id', 'start_date', 'end_date', 'summary', 'category') + + def get_title(self, obj): + return ' %s - %s ' % (obj.category, obj.summary) + + def get_description(self, obj): + result = u'' + result += u'\tDate: %s\n' % obj.start_date.strftime('%A %d %B %Y') + result += u'\tHour: %s - %s \n' % (obj.start_date.strftime('%H:%M'), obj.end_date.strftime('%H:%M')) + return result.strip('\n\t') + + +class UpcomingFormatter(IFormatter): + MANDATORY_FIELDS = ('id', 'start_date', 'end_date', 'summary', 'category') + + def format_obj(self, obj, alias): + result = u'%s%s - %s%s\n' % (self.BOLD, obj.category, obj.summary, self.NC) + result += u'Date: %s\n' % obj.start_date.strftime('%A %d %B %Y') + result += u'Hour: %s - %s\n' % (obj.start_date.strftime('%H:%M'), obj.end_date.strftime('%H:%M')) + + if hasattr(obj, 'location') and not empty(obj.location): + result += u'Location: %s\n' % obj.location + + if hasattr(obj, 'description') and not empty(obj.description): + result += u'Description:\n %s\n\n' % obj.description + + if hasattr(obj, 'price') and not empty(obj.price): + result += u'Price: %s\n' % obj.price + + if hasattr(obj, 'url') and not empty(obj.url): + result += u'url: %s\n' % obj.url + + return result + + +class Boobcoming(ReplApplication): + APPNAME = 'boobcoming' + VERSION = '0.h' + COPYRIGHT = 'Copyright(C) 2012 Bezleputh' + DESCRIPTION = "Console application to see upcoming events." + SHORT_DESCRIPTION = "see upcoming events" + CAPS = ICapCalendarEvent + EXTRA_FORMATTERS = {'upcoming_list': UpcomingListFormatter, + 'upcoming': UpcomingFormatter, + #'ical_formatter': ICalFormatter, + } + COMMANDS_FORMATTERS = {'list': 'upcoming_list', + 'info': 'upcoming', + #'export': 'ical_formatter', + } + + WEEK = {'MONDAY': 0, + 'TUESDAY': 1, + 'WEDNESDAY': 2, + 'THURSDAY': 3, + 'FRIDAY': 4, + 'SATURDAY': 5, + 'SUNDAY': 6, + 'LUNDI': 0, + 'MARDI': 1, + 'MERCREDI': 2, + 'JEUDI': 3, + 'VENDREDI': 4, + 'SAMEDI': 5, + 'DIMANCHE': 6, + } + + @defaultcount(10) + def do_list(self, line): + """ + list [PATTERN] + List upcoming events, pattern can be an english or french week day, 'today' or a date + """ + + self.change_path([u'events']) + if line: + _date = self.parse_date(line) + if not _date: + print >>sys.stderr, 'Invalid argument: %s' % self.get_command_help('list', short=True) + return 2 + + date_from = datetime.combine(_date, time.min) + date_to = datetime.combine(_date, time.max) + else: + date_from = datetime.now() + date_to = None + + for backend, event in self.do('list_events', date_from, date_to): + self.cached_format(event) + + def complete_info(self, text, line, *ignored): + args = line.split(' ') + if len(args) == 2: + return self._complete_object() + + def do_info(self, _id): + """ + info ID + + Get information about an event. + """ + + if not _id: + print >>sys.stderr, 'This command takes an argument: %s' % self.get_command_help('info', short=True) + return 2 + + event = self.get_object(_id, 'get_event') + + if not event: + print >>sys.stderr, 'Upcoming event not found: %s' % _id + return 3 + + self.start_format() + self.format(event) + + def start_format(self, **kwargs): + result = u'BEGIN:VCALENDAR\n' + result += u'VERSION:2.0\n' + result += u'PRODID:-//hacksw/handcal//NONSGML v1.0//EN\n' + return result + + def format_event(self, obj): + result = u'BEGIN:VEVENT\n' + result += u'DTSTART:%s\n' % obj.start_date.strftime("%Y%m%dT%H%M%SZ") + result += u'DTEND:%s\n' % obj.end_date.strftime("%Y%m%dT%H%M%SZ") + result += u'SUMMARY:%s\n' % obj.summary + if hasattr(obj, 'location') and not empty(obj.location): + result += u'LOCATION:%s\n' % obj.location + + if hasattr(obj, 'categories') and not empty(obj.categories): + result += u'CATEGORIES:%s\n' % obj.categories + + if hasattr(obj, 'status') and not empty(obj.status): + result += u'STATUS:%s\n' % obj.status + + if hasattr(obj, 'description') and not empty(obj.description): + result += u'DESCRIPTION:%s\n' % obj.description.replace('\r\n', '\\n') + + if hasattr(obj, 'transp') and not empty(obj.transp): + result += u'TRANSP:%s\n' % obj.transp + + if hasattr(obj, 'sequence') and not empty(obj.sequence): + result += u'SEQUENCE:%s\n' % obj.sequence + + if hasattr(obj, 'url') and not empty(obj.url): + result += u'URL:%s\n' % obj.url + + result += u'END:VEVENT\n' + return result + + def end_format(self): + return u'END:VCALENDAR' + + def do_export(self, line): + """ + export FILENAME ID1 [ID2 ID3 ...] + + id is the identifier of the event + FILENAME is where to write the file. If FILENAME is '-', the file is written to stdout. + + Export event in ICALENDAR format + """ + if not line: + print >>sys.stderr, 'This command takes two arguments: %s' % self.get_command_help('export') + return 2 + + _file, args = self.parse_command_args(line, 2, req_n=1) + + if not args: + print >>sys.stderr, 'This command takes two arguments: %s' % self.get_command_help('export') + return 2 + + _ids = args.strip().split(' ') + + buff = self.start_format() + + for _id in _ids: + event = self.get_object(_id, 'get_event') + + if not event: + print >>sys.stderr, 'Upcoming event not found: %s' % _id + return 3 + + buff += self.format_event(event) + + buff += self.end_format() + + if _file == "-": + print buff + else: + try: + dest = self.check_file_ext(_file) + with open(dest, 'w') as f: + f.write(buff.encode('ascii', 'ignore')) + except IOError as e: + print >>sys.stderr, 'Unable to write bill in "%s": %s' % (dest, e) + return 1 + + def check_file_ext(self, _file): + splitted_file = _file.split('.') + if splitted_file[-1] != 'ics': + return "%s.ics" % _file + else: + return _file + + def get_date_from_day(self, day): + today = date.today() + today_day_number = today.weekday() + + requested_day_number = self.WEEK[day.upper()] + + if today_day_number < requested_day_number: + day_to_go = requested_day_number - today_day_number + else: + day_to_go = 7 - today_day_number + requested_day_number + + requested_date = today + timedelta(day_to_go) + return date(requested_date.year, requested_date.month, requested_date.day) + + def parse_date(self, string): + matches = re.search('\s*([012]?[0-9]|3[01])\s*/\s*(0?[1-9]|1[012])\s*/?(\d{2}|\d{4})?$', string) + if matches: + year = matches.group(3) + if not year: + year = date.today().year + elif len(year) == 2: + year = 2000 + int(year) + return date(int(year), int(matches.group(2)), int(matches.group(1))) + + elif string.upper() in self.WEEK.keys(): + return self.get_date_from_day(string) + + elif string.upper() == "TODAY": + return date.today()