Source code for ics.icalendar

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals, absolute_import

from six import PY2, PY3, StringIO, string_types, text_type, integer_types
from six.moves import filter, map, range

from dateutil.tz import tzical
import copy
import collections

from .component import Component
from .timeline import Timeline
from .event import Event
from .todo import Todo
from .parse import (
    lines_to_container,
    string_to_container,
    ContentLine,
    Container,
)
from .utils import remove_x


[docs]class Calendar(Component): """Represents an unique rfc5545 iCalendar.""" _TYPE = 'VCALENDAR' _EXTRACTORS = [] _OUTPUTS = []
[docs] def __init__(self, imports=None, events=None, todos=None, creator=None): """Instantiates a new Calendar. Args: imports (string or list of lines/strings): data to be imported into the Calendar(), events (list of Event): :class:`ics.event.Event`s to be added to the calendar todos (list of Todo): :class:`ics.event.Todo`s to be added to the calendar creator (string): uid of the creator program. If `imports` is specified, every other argument will be ignored. """ # TODO : implement a file-descriptor import and a filename import self._timezones = {} self.events = set() self.todos = set() self._unused = Container(name='VCALENDAR') self.scale = None self.method = None self.timeline = Timeline(self) if imports is not None: if isinstance(imports, string_types): container = string_to_container(imports) elif isinstance(imports, collections.Iterable): container = lines_to_container(imports) else: raise TypeError("Expecting a sequence or a string") # TODO : make a better API for multiple calendars if len(container) != 1: raise NotImplementedError( 'Multiple calendars in one file are not supported') self._populate(container[0]) # Use first calendar else: if events is not None: self.events.update(set(events)) if todos is not None: self.todos.update(set(todos)) self._creator = creator
[docs] def __urepr__(self): """Returns: unicode: representation (__repr__) of the calendar. Should not be used directly. Use self.__repr__ instead. """ return "<Calendar with {} event{} and {} todo{}>" \ .format(len(self.events), "s" if len(self.events) > 1 else "", len(self.todos), "s" if len(self.todos) > 1 else "")
[docs] def __iter__(self): """Returns: iterable: an iterable version of __str__, line per line (with line-endings). Example: Can be used to write calendar to a file: >>> c = Calendar(); c.append(Event(name="My cool event")) >>> open('my.ics', 'w').writelines(c) """ for line in str(self).split('\n'): l = line + '\n' if PY2: l = l.encode('utf-8') yield l
[docs] def __eq__(self, other): for attr in ('_unused', 'scale', 'method', 'creator'): if self.__getattribute__(attr) != other.__getattribute__(attr): return False return (self.events == other.events) and (self.todos == other.todos)
[docs] def __ne__(self, other): return not self.__eq__(other)
@property def creator(self): """Get or set the calendar's creator. | Will return a string. | May be set to a string. | Creator is the PRODID iCalendar property. | It uniquely identifies the program that created the calendar. """ return self._creator @creator.setter def creator(self, value): if not isinstance(value, text_type): raise ValueError('Event.creator must be unicode data not {}'.format(type(value))) self._creator = value
[docs] def clone(self): """ Returns: Calendar: an exact deep copy of self """ clone = copy.copy(self) clone._unused = clone._unused.clone() clone.events = copy.copy(self.events) clone.todos = copy.copy(self.todos) clone._timezones = copy.copy(self._timezones) return clone
# ------------------ # ----- Inputs ----- # ------------------ @Calendar._extracts('PRODID', required=True) def prodid(calendar, prodid): calendar._creator = prodid.value __version_default__ = [ContentLine(name='VERSION', value='2.0')] @Calendar._extracts('VERSION', required=True, default=__version_default__) def version(calendar, line): version = line # TODO : should take care of minver/maxver if ';' in version.value: _, calendar.version = version.value.split(';') else: calendar.version = version.value @Calendar._extracts('CALSCALE') def scale(calendar, line): calscale = line if calscale: calendar.scale = calscale.value.lower() calendar.scale_params = calscale.params else: calendar.scale = 'georgian' calendar.scale_params = {} @Calendar._extracts('METHOD') def method(calendar, line): method = line if method: calendar.method = method.value calendar.method_params = method.params else: calendar.method = None calendar.method_params = {} @Calendar._extracts('VTIMEZONE', multiple=True) def timezone(calendar, vtimezones): """Receives a list of VTIMEZONE blocks. Parses them and adds them to calendar._timezones. """ for vtimezone in vtimezones: remove_x(vtimezone) # Remove non standard lines from the block fake_file = StringIO() fake_file.write(str(vtimezone)) # Represent the block as a string fake_file.seek(0) timezones = tzical(fake_file) # tzical does not like strings # timezones is a tzical object and could contain multiple timezones for key in timezones.keys(): calendar._timezones[key] = timezones.get(key) @Calendar._extracts('VEVENT', multiple=True) def events(calendar, lines): # tz=calendar._timezones gives access to the event factory to the # timezones list def event_factory(x): return Event._from_container(x, tz=calendar._timezones) calendar.events = list(map(event_factory, lines)) @Calendar._extracts('VTODO', multiple=True) def todos(calendar, lines): # tz=calendar._timezones gives access to the event factory to the # timezones list def todo_factory(x): return Todo._from_container(x, tz=calendar._timezones) calendar.todos = list(map(todo_factory, lines)) # ------------------- # ----- Outputs ----- # ------------------- @Calendar._outputs def o_prodid(calendar, container): creator = calendar.creator if calendar.creator else \ 'ics.py - http://git.io/lLljaA' container.append(ContentLine('PRODID', value=creator)) @Calendar._outputs def o_version(calendar, container): container.append(ContentLine('VERSION', value='2.0')) @Calendar._outputs def o_scale(calendar, container): if calendar.scale: container.append(ContentLine('CALSCALE', value=calendar.scale.upper())) @Calendar._outputs def o_method(calendar, container): if calendar.method: container.append(ContentLine('METHOD', value=calendar.method.upper())) @Calendar._outputs def o_events(calendar, container): for event in calendar.events: container.append(str(event)) @Calendar._outputs def o_todos(calendar, container): for todo in calendar.todos: container.append(str(todo))