def ics_from_list(events, tzs, random_uid=False): """convert an iterable of icalendar.Events to an icalendar.Calendar :params events: list of events all with the same uid :type events: list(icalendar.cal.Event) :param random_uid: assign random uids to all events :type random_uid: bool :param tzs: collection of timezones :type tzs: dict(icalendar.cal.Vtimzone """ calendar = icalendar.Calendar() calendar.add('version', '2.0') calendar.add('prodid', '-//CALENDARSERVER.ORG//NONSGML Version 1//EN') if random_uid: new_uid = generate_random_uid() needed_tz, missing_tz = set(), set() for sub_event in events: if random_uid: sub_event['UID'] = new_uid # icalendar round-trip converts `TZID=a b` to `TZID="a b"` investigate, file bug XXX for prop in ['DTSTART', 'DTEND', 'DUE', 'EXDATE', 'RDATE', 'RECURRENCE-ID', 'DUE']: if isinstance(sub_event.get(prop), list): items = sub_event.get(prop) else: items = [sub_event.get(prop)] for item in items: if not (hasattr(item, 'dt') or hasattr(item, 'dts')): continue # if prop is a list, all items have the same parameters datetime_ = item.dts[0].dt if hasattr(item, 'dts') else item.dt if not hasattr(datetime_, 'tzinfo'): continue # check for datetimes' timezones which are not understood by # icalendar if datetime_.tzinfo is None and 'TZID' in item.params and \ item.params['TZID'] not in missing_tz: logger.warning( 'Cannot find timezone `{}` in .ics file, using default timezone. ' 'This can lead to erroneous time shifts'.format(item.params['TZID']) ) missing_tz.add(item.params['TZID']) elif datetime_.tzinfo != pytz.UTC: needed_tz.add(datetime_.tzinfo) for tzid in needed_tz: if str(tzid) in tzs: calendar.add_component(tzs[str(tzid)]) else: logger.warning( 'Cannot find timezone `{}` in .ics file, this could be a bug, ' 'please report this issue at http://github.com/pimutils/khal/.'.format(tzid)) for sub_event in events: calendar.add_component(sub_event) return calendar.to_ical().decode('utf-8')
def import_event(vevent, collection, locale, batch, format=None, env=None): """import one event into collection, let user choose the collection :type vevent: list of vevents, which can be more than one VEVENT, i.e., the same UID, i.e., one "master" event and (optionally) 1+ RECURRENCE-ID events :type vevent: list(str) """ # print all sub-events if not batch: for item in icalendar.Calendar.from_ical(vevent).walk(): if item.name == 'VEVENT': event = Event.fromVEvents( [item], calendar=collection.default_calendar_name, locale=locale) echo(event.format(format, datetime.now(), env=env)) # get the calendar to insert into if batch or len(collection.writable_names) == 1: calendar_name = collection.writable_names[0] else: calendar_names = sorted(collection.writable_names) choices = ', '.join([ '{}({})'.format(name, num) for num, name in enumerate(calendar_names) ]) while True: value = prompt( "Which calendar do you want to import to? (unique prefixes are fine)\n" "{}".format(choices), default=collection.default_calendar_name, ) try: calendar_name = calendar_names[int(value)] break except (ValueError, IndexError): matches = [ x for x in collection.writable_names if x.startswith(value) ] if len(matches) == 1: calendar_name = matches[0] break echo('invalid choice') if batch or confirm("Do you want to import this event into `{}`?".format( calendar_name)): try: collection.new(Item(vevent), collection=calendar_name) except DuplicateUid: if batch or confirm( "An event with the same UID already exists. Do you want to update it?" ): collection.force_update(Item(vevent), collection=calendar_name) else: logger.warning("Not importing event with UID `{}`".format( event.uid))
def import_event(vevent, collection, locale, batch, format=None, env=None): """import one event into collection, let user choose the collection :type vevent: list of vevents, which can be more than one VEVENT, i.e., the same UID, i.e., one "master" event and (optionally) 1+ RECURRENCE-ID events :type vevent: list(str) """ # print all sub-events if not batch: for item in icalendar.Calendar.from_ical(vevent).walk(): if item.name == 'VEVENT': event = Event.fromVEvents( [item], calendar=collection.default_calendar_name, locale=locale) echo(event.format(format, datetime.now(), env=env)) # get the calendar to insert into if not collection.writable_names: raise ConfigurationError('No writable calendars found, aborting import.') if len(collection.writable_names) == 1: calendar_name = collection.writable_names[0] elif batch: calendar_name = collection.default_calendar_name else: calendar_names = sorted(collection.writable_names) choices = ', '.join( ['{}({})'.format(name, num) for num, name in enumerate(calendar_names)]) while True: value = prompt( "Which calendar do you want to import to? (unique prefixes are fine)\n" "{}".format(choices), default=collection.default_calendar_name, ) try: calendar_name = calendar_names[int(value)] break except (ValueError, IndexError): matches = [x for x in collection.writable_names if x.startswith(value)] if len(matches) == 1: calendar_name = matches[0] break echo('invalid choice') assert calendar_name in collection.writable_names if batch or confirm("Do you want to import this event into `{}`?".format(calendar_name)): try: collection.new(Item(vevent), collection=calendar_name) except DuplicateUid: if batch or confirm( "An event with the same UID already exists. Do you want to update it?"): collection.force_update(Item(vevent), collection=calendar_name) else: logger.warning("Not importing event with UID `{}`".format(event.uid))
def parse_config(self, cfile): self._conf_parser = RawConfigParser() try: if not self._conf_parser.read(cfile): logger.error("Cannot read config file' {}'".format(cfile)) return None except ConfigParserError as error: logger.error("Could not parse config file " "'{}': {}".format(cfile, error)) return None items = dict() failed = False for section in self._conf_parser.sections(): parser = self._get_section_parser(section) if parser is None: logger.warning( "Found unknown section '{}' in config file".format(section) ) continue values = parser.parse(section) if values is None: failed = True continue if parser.is_collection(): if parser.group not in items: items[parser.group] = [] items[parser.group].append(values) else: items[parser.group] = values failed = self.check_required(items) or failed self.warn_leftovers() self.dump(items) if failed: return None else: return Namespace(items)
def parse_config(self, cfile): self._conf_parser = RawConfigParser() try: if not self._conf_parser.read(cfile): logger.error("Cannot read config file' {}'".format(cfile)) return None except ConfigParserError as error: logger.error("Could not parse config file " "'{}': {}".format(cfile, error)) return None items = dict() failed = False for section in self._conf_parser.sections(): parser = self._get_section_parser(section) if parser is None: logger.warning( "Found unknown section '{}' in config file".format( section)) continue values = parser.parse(section) if values is None: failed = True continue if parser.is_collection(): if parser.group not in items: items[parser.group] = [] items[parser.group].append(values) else: items[parser.group] = values failed = self.check_required(items) or failed self.warn_leftovers() self.dump(items) if failed: return None else: return Namespace(items)