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()