# HG changeset patch # User Paul Boddie # Date 1463086737 -7200 # Node ID 2fffc03fa3ef5c8e2459bccfa4d1085cde2c2910 # Parent 318433c9a1c21070726d67a17ab3082b8bf1335f Introduced line length configuration for more convenient testing of output. Moved tabular file parsing to the text module for potential use by the tools. diff -r 318433c9a1c2 -r 2fffc03fa3ef imiptools/__init__.py --- a/imiptools/__init__.py Thu May 12 17:51:10 2016 +0200 +++ b/imiptools/__init__.py Thu May 12 22:58:57 2016 +0200 @@ -194,6 +194,14 @@ self.publishing_dir = getvalue(publishing_dir) self.preferences_dir = getvalue(preferences_dir) self.journal_dir = getvalue(journal_dir) + + # If debug mode is set, extend the line length for convenience. + + if self.debug: + config.CALENDAR_LINE_LENGTH = 1000 + + # Process the input. + self.process(stream, original_recipients) def __call__(self): diff -r 318433c9a1c2 -r 2fffc03fa3ef imiptools/client.py --- a/imiptools/client.py Thu May 12 17:51:10 2016 +0200 +++ b/imiptools/client.py Thu May 12 22:58:57 2016 +0200 @@ -21,8 +21,8 @@ from datetime import datetime, timedelta from imiptools import config -from imiptools.data import Object, get_address, get_uri, get_window_end, \ - is_new_object, make_freebusy, to_part, \ +from imiptools.data import Object, check_delegation, get_address, get_uri, \ + get_window_end, is_new_object, make_freebusy, to_part, \ uri_dict, uri_item, uri_items, uri_parts, uri_values from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \ get_duration, get_timestamp @@ -270,7 +270,7 @@ user_attr = {} self.update_sender(user_attr) - return to_part("PUBLISH", [make_freebusy(freebusy, uid, self.user, user_attr)]) + return self.to_part("PUBLISH", [make_freebusy(freebusy, uid, self.user, user_attr)]) return None @@ -301,6 +301,20 @@ else: freebusy.update_freebusy(periods, transp, uid, recurrenceid, summary, organiser) + # Preparation of content. + + def to_part(self, method, fragments): + + "Return an encoded MIME part for the given 'method' and 'fragments'." + + return to_part(method, fragments, line_length=config.CALENDAR_LINE_LENGTH) + + def object_to_part(self, method, obj): + + "Return an encoded MIME part for the given 'method' and 'obj'." + + return obj.to_part(method, line_length=config.CALENDAR_LINE_LENGTH) + # Preparation of messages communicating the state of events. def get_message_parts(self, obj, method, attendee=None): @@ -327,7 +341,7 @@ if not attendee or self.is_participating(attendee, obj=obj): organiser, organiser_attr = uri_item(obj.get_item("ORGANIZER")) self.update_sender(organiser_attr) - responses.append(obj.to_part(method)) + responses.append(self.object_to_part(method, obj)) methods.add(method) # Get recurrences for parent events. @@ -350,7 +364,7 @@ if not attendee or self.is_participating(attendee, obj=obj): organiser, organiser_attr = uri_item(obj.get_item("ORGANIZER")) self.update_sender(organiser_attr) - responses.append(obj.to_part(rmethod)) + responses.append(self.object_to_part(rmethod, obj)) methods.add(rmethod) return methods, responses @@ -455,7 +469,7 @@ continue obj["RECURRENCE-ID"] = obj["DTSTART"] = [(format_datetime(p.get_start()), p.get_start_attr())] obj["DTEND"] = [(format_datetime(p.get_end()), p.get_end_attr())] - unscheduled_parts.append(obj.to_part("CANCEL")) + unscheduled_parts.append(self.object_to_part("CANCEL", obj)) return unscheduled_parts @@ -700,7 +714,7 @@ self.update_senders(obj=obj) obj.update_dtstamp() obj.update_sequence(False) - self._send_message(get_address(self.user), [get_address(attendee)], [obj.to_part(method)], True) + self._send_message(get_address(self.user), [get_address(attendee)], [self.object_to_part(method, obj)], True) return True def process_received_request(self, changed=False): @@ -726,7 +740,8 @@ self.update_dtstamp() self.update_sequence(False) - self.send_message([self.obj.to_part(changed and "COUNTER" or "REPLY")], get_address(self.user), self.obj, False, True) + self.send_message([self.object_to_part(changed and "COUNTER" or "REPLY", self.obj)], + get_address(self.user), self.obj, False, True) return True def process_created_request(self, method, to_cancel=None, to_unschedule=None): @@ -782,7 +797,7 @@ # Send a cancellation to all uninvited attendees. - parts = [obj.to_part("CANCEL")] + parts = [self.object_to_part("CANCEL", obj)] self.send_message(parts, get_address(organiser), obj, True, False) # Issue a CANCEL message to the user's address. diff -r 318433c9a1c2 -r 2fffc03fa3ef imiptools/config.py --- a/imiptools/config.py Thu May 12 17:51:10 2016 +0200 +++ b/imiptools/config.py Thu May 12 22:58:57 2016 +0200 @@ -134,4 +134,9 @@ IMIP_COUNTER_AS_REQUEST = True +# Calendar object maximum line length. If None, the recommended iCalendar line +# length is used (which should be 76 characters). + +CALENDAR_LINE_LENGTH = None + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 318433c9a1c2 -r 2fffc03fa3ef imiptools/data.py --- a/imiptools/data.py Thu May 12 17:51:10 2016 +0200 +++ b/imiptools/data.py Thu May 12 22:58:57 2016 +0200 @@ -161,14 +161,16 @@ def get_duration(self, name): return get_duration(self.get_value(name)) + # Serialisation. + def to_node(self): return to_node({self.objtype : [(self.details, self.attr)]}) - def to_part(self, method): - return to_part(method, [self.to_node()]) + def to_part(self, method, encoding="utf-8", line_length=None): + return to_part(method, [self.to_node()], encoding, line_length) - def to_string(self): - return to_string(self.to_node()) + def to_string(self, encoding="utf-8", line_length=None): + return to_string(self.to_node(), encoding, line_length) # Direct access to the structure. @@ -615,7 +617,7 @@ # Get a constrained view if start and end limits are specified. if period: - periods = freebusy.period_overlaps(period, True) + periods = freebusy.get_overlapping(period) else: periods = freebusy @@ -677,17 +679,16 @@ return parse_object(StringIO(s), encoding, objtype) -def to_part(method, calendar): +def to_part(method, fragments, encoding="utf-8", line_length=None): """ - Write using the given 'method', the 'calendar' details to a MIME + Write using the given 'method', the given 'fragments' to a MIME text/calendar part. """ - encoding = "utf-8" out = StringIO() try: - to_stream(out, make_calendar(calendar, method), encoding) + to_stream(out, make_calendar(fragments, method), encoding, line_length) part = MIMEText(out.getvalue(), "calendar", encoding) part.set_param("method", method) return part @@ -695,19 +696,19 @@ finally: out.close() -def to_stream(out, fragment, encoding="utf-8"): +def to_stream(out, fragment, encoding="utf-8", line_length=None): "Write to the 'out' stream the given 'fragment'." - iterwrite(out, encoding=encoding).append(fragment) + iterwrite(out, encoding=encoding, line_length=line_length).append(fragment) -def to_string(fragment, encoding="utf-8"): +def to_string(fragment, encoding="utf-8", line_length=None): "Return a string encoding the given 'fragment'." out = StringIO() try: - to_stream(out, fragment, encoding) + to_stream(out, fragment, encoding, line_length) return out.getvalue() finally: diff -r 318433c9a1c2 -r 2fffc03fa3ef imiptools/stores/file.py --- a/imiptools/stores/file.py Thu May 12 17:51:10 2016 +0200 +++ b/imiptools/stores/file.py Thu May 12 22:58:57 2016 +0200 @@ -29,7 +29,7 @@ from imiptools.period import FreeBusyPeriod, FreeBusyGroupPeriod, \ FreeBusyOfferPeriod, FreeBusyCollection, \ FreeBusyGroupCollection, FreeBusyOffersCollection -from imiptools.text import parse_line +from imiptools.text import get_table, set_defaults from os.path import isdir, isfile, join from os import listdir, remove, rmdir import codecs @@ -47,18 +47,13 @@ # Utility methods. def _set_defaults(self, t, empty_defaults): - for i, default in empty_defaults: - if i >= len(t): - t += [None] * (i - len(t) + 1) - if not t[i]: - t[i] = default - return t + return set_defaults(t, empty_defaults) - def _get_table(self, user, filename, empty_defaults=None, tab_separated=True): + def _get_table(self, filename, empty_defaults=None, tab_separated=True): """ - From the file for the given 'user' having the given 'filename', return - a list of tuples representing the file's contents. + From the file having the given 'filename', return a list of tuples + representing the file's contents. The 'empty_defaults' is a list of (index, value) tuples indicating the default value where a column either does not exist or provides an empty @@ -69,21 +64,7 @@ splitting each line of the file using tab characters as separators. """ - f = codecs.open(filename, "rb", encoding="utf-8") - try: - l = [] - for line in f.readlines(): - line = line.strip(" \r\n") - if tab_separated: - t = line.split("\t") - else: - t = parse_line(line) - if empty_defaults: - t = self._set_defaults(t, empty_defaults) - l.append(tuple(t)) - return l - finally: - f.close() + return get_table(filename, empty_defaults, tab_separated) def _get_table_atomic(self, user, filename, empty_defaults=None, tab_separated=True): @@ -102,15 +83,14 @@ self.acquire_lock(user) try: - return self._get_table(user, filename, empty_defaults, tab_separated) + return self._get_table(filename, empty_defaults, tab_separated) finally: self.release_lock(user) - def _set_table(self, user, filename, items, empty_defaults=None): + def _set_table(self, filename, items, empty_defaults=None): """ - For the given 'user', write to the file having the given 'filename' the - 'items'. + Write to the file having the given 'filename' the 'items'. The 'empty_defaults' is a list of (index, value) tuples indicating the default value where a column either does not exist or provides an empty @@ -146,7 +126,7 @@ self.acquire_lock(user) try: - self._set_table(user, filename, items, empty_defaults) + self._set_table(filename, items, empty_defaults) finally: self.release_lock(user) diff -r 318433c9a1c2 -r 2fffc03fa3ef imiptools/text.py --- a/imiptools/text.py Thu May 12 17:51:10 2016 +0200 +++ b/imiptools/text.py Thu May 12 22:58:57 2016 +0200 @@ -3,7 +3,7 @@ """ Parsing of textual content. -Copyright (C) 2016 Paul Boddie +Copyright (C) 2014, 2015, 2016 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -19,6 +19,7 @@ this program. If not, see . """ +import codecs import re # Parsing of lines to obtain functions and arguments. @@ -53,4 +54,72 @@ return parts +# Parsing of tabular files. + +def set_defaults(t, empty_defaults): + + """ + In the list 't', replace values that are empty or absent with defaults + provided by the 'empty_defaults' collection whose entries are of the form + (index, value). + """ + + for i, default in empty_defaults: + if i >= len(t): + t += [None] * (i - len(t) + 1) + if not t[i]: + t[i] = default + return t + +def get_table(filename, empty_defaults=None, tab_separated=True): + + """ + From the file having the given 'filename', return a list of tuples + representing the file's contents. + + The 'empty_defaults' is a list of (index, value) tuples indicating the + default value where a column either does not exist or provides an empty + value. + + If 'tab_separated' is specified and is a false value, line parsing using + the imiptools.text.parse_line function will be performed instead of + splitting each line of the file using tab characters as separators. + """ + + f = codecs.open(filename, "rb", encoding="utf-8") + try: + return get_table_from_stream(f, empty_defaults, tab_separated) + finally: + f.close() + +def get_table_from_stream(f, empty_defaults=None, tab_separated=True): + + """ + Return a list of tuples representing the contents of the stream 'f'. + + The 'empty_defaults' is a list of (index, value) tuples indicating the + default value where a column either does not exist or provides an empty + value. + + If 'tab_separated' is specified and is a false value, line parsing using + the imiptools.text.parse_line function will be performed instead of + splitting each line of the file using tab characters as separators. + """ + + l = [] + + for line in f.readlines(): + line = line.strip(" \r\n") + + if tab_separated: + t = line.split("\t") + else: + t = parse_line(line) + + if empty_defaults: + t = set_defaults(t, empty_defaults) + l.append(tuple(t)) + + return l + # vim: tabstop=4 expandtab shiftwidth=4