# HG changeset patch # User Paul Boddie # Date 1327187056 -3600 # Node ID bf446fb77924d6b788a625e3f3417e9e0a43940e The MoinSupport distribution is derived from the EventAggregatorSupport module and provides different modules for various common MoinMoin extension-related activities. diff -r 000000000000 -r bf446fb77924 DateSupport.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DateSupport.py Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,897 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - DateSupport library (derived from EventAggregatorSupport) + + @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +import calendar +import datetime +import re +import bisect + +try: + import pytz +except ImportError: + pytz = None + +__version__ = "0.1" + +# Date labels. + +month_labels = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"] +weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + +# Month, date, time and datetime parsing. + +month_regexp_str = ur'(?P[0-9]{4})-(?P[0-9]{2})' +date_regexp_str = ur'(?P[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})' +time_regexp_str = ur'(?P[0-2][0-9]):(?P[0-5][0-9])(?::(?P[0-6][0-9]))?' +timezone_offset_str = ur'(?P(UTC)?(?:(?P[-+])(?P[0-9]{2})(?::?(?P[0-9]{2}))?))' +timezone_olson_str = ur'(?P[a-zA-Z]+(?:/[-_a-zA-Z]+){1,2})' +timezone_utc_str = ur'UTC' +timezone_regexp_str = ur'(?P' + timezone_offset_str + '|' + timezone_olson_str + '|' + timezone_utc_str + ')' +datetime_regexp_str = date_regexp_str + ur'(?:\s+' + time_regexp_str + ur'(?:\s+' + timezone_regexp_str + ur')?)?' + +month_regexp = re.compile(month_regexp_str, re.UNICODE) +date_regexp = re.compile(date_regexp_str, re.UNICODE) +time_regexp = re.compile(time_regexp_str, re.UNICODE) +timezone_olson_regexp = re.compile(timezone_olson_str, re.UNICODE) +timezone_offset_regexp = re.compile(timezone_offset_str, re.UNICODE) +datetime_regexp = re.compile(datetime_regexp_str, re.UNICODE) + +# iCalendar date and datetime parsing. + +date_icalendar_regexp_str = ur'(?P[0-9]{4})(?P[0-9]{2})(?P[0-9]{2})' +datetime_icalendar_regexp_str = date_icalendar_regexp_str + \ + ur'(?:' \ + ur'T(?P[0-2][0-9])(?P[0-5][0-9])(?P[0-6][0-9])' \ + ur'(?PZ)?' \ + ur')?' + +date_icalendar_regexp = re.compile(date_icalendar_regexp_str, re.UNICODE) +datetime_icalendar_regexp = re.compile(datetime_icalendar_regexp_str, re.UNICODE) + +# Utility functions. + +def int_or_none(x): + if x is None: + return x + else: + return int(x) + +def getMonthLabel(month): + + "Return an unlocalised label for the given 'month'." + + return month_labels[month - 1] # zero-based labels + +def getDayLabel(weekday): + + "Return an unlocalised label for the given 'weekday'." + + return weekday_labels[weekday] + +# Interfaces. + +class ActsAsTimespan: + pass + +# Date-related functions. + +def cmp_dates_as_day_start(a, b): + + """ + Compare dates/datetimes 'a' and 'b' treating dates without time information + as the earliest time in a particular day. + """ + + are_equal = a == b + + if are_equal: + a2 = a.as_datetime_or_date() + b2 = b.as_datetime_or_date() + + if isinstance(a2, Date) and isinstance(b2, DateTime): + return -1 + elif isinstance(a2, DateTime) and isinstance(b2, Date): + return 1 + + return cmp(a, b) + +class Convertible: + + "Support for converting temporal objects." + + def _get_converter(self, resolution): + if resolution == "month": + return lambda x: x and x.as_month() + elif resolution == "date": + return lambda x: x and x.as_date() + elif resolution == "datetime": + return lambda x: x and x.as_datetime_or_date() + else: + return lambda x: x + +class Temporal(Convertible): + + "A simple temporal representation, common to dates and times." + + def __init__(self, data): + self.data = list(data) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.data) + + def __hash__(self): + return hash(self.as_tuple()) + + def as_tuple(self): + return tuple(self.data) + + def convert(self, resolution): + return self._get_converter(resolution)(self) + + def __cmp__(self, other): + + """ + The result of comparing this instance with 'other' is derived from a + comparison of the instances' date(time) data at the highest common + resolution, meaning that if a date is compared to a datetime, the + datetime will be considered as a date. Thus, a date and a datetime + referring to the same date will be considered equal. + """ + + if not isinstance(other, Temporal): + return NotImplemented + else: + data = self.as_tuple() + other_data = other.as_tuple() + length = min(len(data), len(other_data)) + return cmp(data[:length], other_data[:length]) + + def __sub__(self, other): + + """ + Return the difference between this object and the 'other' object at the + highest common accuracy of both objects. + """ + + if not isinstance(other, Temporal): + return NotImplemented + else: + data = self.as_tuple() + other_data = other.as_tuple() + if len(data) < len(other_data): + return len(self.until(other)) + else: + return len(other.until(self)) + + def _until(self, start, end, nextfn, prevfn): + + """ + Return a collection of units of time by starting from the given 'start' + and stepping across intervening units until 'end' is reached, using the + given 'nextfn' and 'prevfn' to step from one unit to the next. + """ + + current = start + units = [current] + if current < end: + while current < end: + current = nextfn(current) + units.append(current) + elif current > end: + while current > end: + current = prevfn(current) + units.append(current) + return units + + def ambiguous(self): + + "Only times can be ambiguous." + + return 0 + +class Month(Temporal): + + "A simple year-month representation." + + def __str__(self): + return "%04d-%02d" % self.as_tuple()[:2] + + def as_datetime(self, day, hour, minute, second, zone): + return DateTime(self.as_tuple() + (day, hour, minute, second, zone)) + + def as_date(self, day): + if day < 0: + weekday, ndays = self.month_properties() + day = ndays + 1 + day + return Date(self.as_tuple() + (day,)) + + def as_month(self): + return self + + def year(self): + return self.data[0] + + def month(self): + return self.data[1] + + def month_properties(self): + + """ + Return the weekday of the 1st of the month, along with the number of + days, as a tuple. + """ + + year, month = self.as_tuple()[:2] + return calendar.monthrange(year, month) + + def month_update(self, n=1): + + "Return the month updated by 'n' months." + + year, month = self.as_tuple()[:2] + return Month((year + (month - 1 + n) / 12, (month - 1 + n) % 12 + 1)) + + update = month_update + + def next_month(self): + + "Return the month following this one." + + return self.month_update(1) + + next = next_month + + def previous_month(self): + + "Return the month preceding this one." + + return self.month_update(-1) + + previous = previous_month + + def months_until(self, end): + + "Return the collection of months from this month until 'end'." + + return self._until(self.as_month(), end.as_month(), Month.next_month, Month.previous_month) + + until = months_until + +class Date(Month): + + "A simple year-month-day representation." + + def constrain(self): + year, month, day = self.as_tuple()[:3] + + month = max(min(month, 12), 1) + wd, last_day = calendar.monthrange(year, month) + day = max(min(day, last_day), 1) + + self.data[1:3] = month, day + + def __str__(self): + return "%04d-%02d-%02d" % self.as_tuple()[:3] + + def as_datetime(self, hour, minute, second, zone): + return DateTime(self.as_tuple() + (hour, minute, second, zone)) + + def as_start_of_day(self): + return self.as_datetime(None, None, None, None) + + def as_date(self): + return self + + def as_datetime_or_date(self): + return self + + def as_month(self): + return Month(self.data[:2]) + + def day(self): + return self.data[2] + + def day_update(self, n=1): + + "Return the month updated by 'n' days." + + delta = datetime.timedelta(n) + dt = datetime.date(*self.as_tuple()[:3]) + dt_new = dt + delta + return Date((dt_new.year, dt_new.month, dt_new.day)) + + update = day_update + + def next_day(self): + + "Return the date following this one." + + year, month, day = self.as_tuple()[:3] + _wd, end_day = calendar.monthrange(year, month) + if day == end_day: + if month == 12: + return Date((year + 1, 1, 1)) + else: + return Date((year, month + 1, 1)) + else: + return Date((year, month, day + 1)) + + next = next_day + + def previous_day(self): + + "Return the date preceding this one." + + year, month, day = self.as_tuple()[:3] + if day == 1: + if month == 1: + return Date((year - 1, 12, 31)) + else: + _wd, end_day = calendar.monthrange(year, month - 1) + return Date((year, month - 1, end_day)) + else: + return Date((year, month, day - 1)) + + previous = previous_day + + def days_until(self, end): + + "Return the collection of days from this date until 'end'." + + return self._until(self.as_date(), end.as_date(), Date.next_day, Date.previous_day) + + until = days_until + +class DateTime(Date): + + "A simple date plus time representation." + + def constrain(self): + Date.constrain(self) + + hour, minute, second = self.as_tuple()[3:6] + + if self.has_time(): + hour = max(min(hour, 23), 0) + minute = max(min(minute, 59), 0) + + if second is not None: + second = max(min(second, 60), 0) # support leap seconds + + self.data[3:6] = hour, minute, second + + def __str__(self): + return Date.__str__(self) + self.time_string() + + def time_string(self): + if self.has_time(): + data = self.as_tuple() + time_str = " %02d:%02d" % data[3:5] + if data[5] is not None: + time_str += ":%02d" % data[5] + if data[6] is not None: + time_str += " %s" % data[6] + return time_str + else: + return "" + + def as_HTTP_datetime_string(self): + weekday = calendar.weekday(*self.data[:3]) + return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (( + getDayLabel(weekday), + self.data[2], + getMonthLabel(self.data[1]), + self.data[0] + ) + tuple(self.data[3:6])) + + def as_datetime(self): + return self + + def as_date(self): + return Date(self.data[:3]) + + def as_datetime_or_date(self): + + """ + Return a date for this datetime if fields are missing. Otherwise, return + this datetime itself. + """ + + if not self.has_time(): + return self.as_date() + else: + return self + + def __cmp__(self, other): + + """ + The result of comparing this instance with 'other' is, if both instances + are datetime instances, derived from a comparison of the datetimes + converted to UTC. If one or both datetimes cannot be converted to UTC, + the datetimes are compared using the basic temporal comparison which + compares their raw time data. + """ + + this = self + + if this.has_time(): + if isinstance(other, DateTime): + if other.has_time(): + this_utc = this.to_utc() + other_utc = other.to_utc() + if this_utc is not None and other_utc is not None: + return cmp(this_utc.as_tuple(), other_utc.as_tuple()) + else: + other = other.padded() + else: + this = this.padded() + + return Date.__cmp__(this, other) + + def has_time(self): + + """ + Return whether this object has any time information. Objects without + time information can refer to the very start of a day. + """ + + return self.data[3] is not None and self.data[4] is not None + + def time(self): + return self.data[3:] + + def seconds(self): + return self.data[5] + + def time_zone(self): + return self.data[6] + + def set_time_zone(self, value): + self.data[6] = value + + def padded(self, empty_value=0): + + """ + Return a datetime with missing fields defined as being the given + 'empty_value' or 0 if not specified. + """ + + data = [] + for x in self.data[:6]: + if x is None: + data.append(empty_value) + else: + data.append(x) + + data += self.data[6:] + return DateTime(data) + + def to_utc(self): + + """ + Return this object converted to UTC, or None if such a conversion is not + defined. + """ + + if not self.has_time(): + return None + + offset = self.utc_offset() + if offset: + hours, minutes = offset + + # Invert the offset to get the correction. + + hours, minutes = -hours, -minutes + + # Get the components. + + hour, minute, second, zone = self.time() + date = self.as_date() + + # Add the minutes and hours. + + minute += minutes + if minute < 0 or minute > 59: + hour += minute / 60 + minute = minute % 60 + + # NOTE: This makes various assumptions and probably would not work + # NOTE: for general arithmetic. + + hour += hours + if hour < 0: + date = date.previous_day() + hour += 24 + elif hour > 23: + date = date.next_day() + hour -= 24 + + return date.as_datetime(hour, minute, second, "UTC") + + # Cannot convert. + + else: + return None + + def utc_offset(self): + + "Return the UTC offset in hours and minutes." + + zone = self.time_zone() + if not zone: + return None + + # Support explicit UTC zones. + + if zone == "UTC": + return 0, 0 + + # Attempt to return a UTC offset where an explicit offset has been set. + + match = timezone_offset_regexp.match(zone) + if match: + if match.group("sign") == "-": + sign = -1 + else: + sign = 1 + + hours = int(match.group("hours")) * sign + minutes = int(match.group("minutes") or 0) * sign + return hours, minutes + + # Attempt to handle Olson time zone identifiers. + + dt = self.as_olson_datetime() + if dt: + seconds = dt.utcoffset().seconds + hours = seconds / 3600 + minutes = (seconds % 3600) / 60 + return hours, minutes + + # Otherwise return None. + + return None + + def olson_identifier(self): + + "Return the Olson identifier from any zone information." + + zone = self.time_zone() + if not zone: + return None + + # Attempt to match an identifier. + + match = timezone_olson_regexp.match(zone) + if match: + return match.group("olson") + else: + return None + + def _as_olson_datetime(self, hours=None): + + """ + Return a Python datetime object for this datetime interpreted using any + Olson time zone identifier and the given 'hours' offset, raising one of + the pytz exceptions in case of ambiguity. + """ + + olson = self.olson_identifier() + if olson and pytz: + tz = pytz.timezone(olson) + data = self.padded().as_tuple()[:6] + dt = datetime.datetime(*data) + + # With an hours offset, find a time probably in a previously + # applicable time zone. + + if hours is not None: + td = datetime.timedelta(0, hours * 3600) + dt += td + + ldt = tz.localize(dt, None) + + # With an hours offset, adjust the time to define it within the + # previously applicable time zone but at the presumably intended + # position. + + if hours is not None: + ldt -= td + + return ldt + else: + return None + + def as_olson_datetime(self): + + """ + Return a Python datetime object for this datetime interpreted using any + Olson time zone identifier, choosing the time from the zone before the + period of ambiguity. + """ + + try: + return self._as_olson_datetime() + except (pytz.UnknownTimeZoneError, pytz.AmbiguousTimeError): + + # Try again, using an earlier local time and then stepping forward + # in the chosen zone. + # NOTE: Four hours earlier seems reasonable. + + return self._as_olson_datetime(-4) + + def ambiguous(self): + + "Return whether the time is local and ambiguous." + + try: + self._as_olson_datetime() + except (pytz.UnknownTimeZoneError, pytz.AmbiguousTimeError): + return 1 + + return 0 + +class Timespan(ActsAsTimespan, Convertible): + + """ + A period of time which can be compared against others to check for overlaps. + """ + + def __init__(self, start, end): + self.start = start + self.end = end + + # NOTE: Should perhaps catch ambiguous time problems elsewhere. + + if self.ambiguous() and self.start is not None and self.end is not None and start > end: + self.start, self.end = end, start + + def __repr__(self): + return "%s(%r, %r)" % (self.__class__.__name__, self.start, self.end) + + def __hash__(self): + return hash((self.start, self.end)) + + def as_timespan(self): + return self + + def as_limits(self): + return self.start, self.end + + def ambiguous(self): + return self.start is not None and self.start.ambiguous() or self.end is not None and self.end.ambiguous() + + def convert(self, resolution): + return Timespan(*map(self._get_converter(resolution), self.as_limits())) + + def is_before(self, a, b): + + """ + Return whether 'a' is before 'b'. Since the end datetime of one period + may be the same as the start datetime of another period, and yet the + first period is intended to be concluded by the end datetime and not + overlap with the other period, a different test is employed for datetime + comparisons. + """ + + # Datetimes without times can be equal to dates and be considered as + # occurring before those dates. Generally, datetimes should not be + # produced without time information as getDateTime converts such + # datetimes to dates. + + if isinstance(a, DateTime) and (isinstance(b, DateTime) or not a.has_time()): + return a <= b + else: + return a < b + + def __contains__(self, other): + + """ + This instance is considered to contain 'other' if one is not before or + after the other. If this instance overlaps or coincides with 'other', + then 'other' is regarded as belonging to this instance's time period. + """ + + return self == other + + def __cmp__(self, other): + + """ + Return whether this timespan occupies the same period of time as the + 'other'. Timespans are considered less than others if their end points + precede the other's start point, and are considered greater than others + if their start points follow the other's end point. + """ + + if isinstance(other, ActsAsTimespan): + other = other.as_timespan() + + if self.end is not None and other.start is not None and self.is_before(self.end, other.start): + return -1 + elif self.start is not None and other.end is not None and self.is_before(other.end, self.start): + return 1 + else: + return 0 + + else: + if self.end is not None and self.is_before(self.end, other): + return -1 + elif self.start is not None and self.is_before(other, self.start): + return 1 + else: + return 0 + +class TimespanCollection: + + """ + A class providing a list-like interface supporting membership tests at a + particular resolution in order to maintain a collection of non-overlapping + timespans. + """ + + def __init__(self, resolution, values=None): + self.resolution = resolution + self.values = values or [] + + def as_timespan(self): + return Timespan(*self.as_limits()) + + def as_limits(self): + + "Return the earliest and latest points in time for this collection." + + if not self.values: + return None, None + else: + first, last = self.values[0], self.values[-1] + if isinstance(first, ActsAsTimespan): + first = first.as_timespan().start + if isinstance(last, ActsAsTimespan): + last = last.as_timespan().end + return first, last + + def convert(self, value): + if isinstance(value, ActsAsTimespan): + ts = value.as_timespan() + return ts and ts.convert(self.resolution) + else: + return value.convert(self.resolution) + + def __iter__(self): + return iter(self.values) + + def __len__(self): + return len(self.values) + + def __getitem__(self, i): + return self.values[i] + + def __setitem__(self, i, value): + self.values[i] = value + + def __contains__(self, value): + test_value = self.convert(value) + return test_value in self.values + + def append(self, value): + self.values.append(value) + + def insert(self, i, value): + self.values.insert(i, value) + + def pop(self): + return self.values.pop() + + def insert_in_order(self, value): + bisect.insort_left(self, value) + +def getDate(s): + + "Parse the string 's', extracting and returning a date object." + + dt = getDateTime(s) + if dt is not None: + return dt.as_date() + else: + return None + +def getDateTime(s): + + """ + Parse the string 's', extracting and returning a datetime object where time + information has been given or a date object where time information is + absent. + """ + + m = datetime_regexp.search(s) + if m: + groups = list(m.groups()) + + # Convert date and time data to integer or None. + + return DateTime(map(int_or_none, groups[:6]) + [m.group("zone")]).as_datetime_or_date() + else: + return None + +def getDateFromCalendar(s): + + """ + Parse the iCalendar format string 's', extracting and returning a date + object. + """ + + dt = getDateTimeFromCalendar(s) + if dt is not None: + return dt.as_date() + else: + return None + +def getDateTimeFromCalendar(s): + + """ + Parse the iCalendar format datetime string 's', extracting and returning a + datetime object where time information has been given or a date object where + time information is absent. + """ + + m = datetime_icalendar_regexp.search(s) + if m: + groups = list(m.groups()) + + # Convert date and time data to integer or None. + + return DateTime(map(int_or_none, groups[:6]) + [m.group("utc") and "UTC" or None]).as_datetime_or_date() + else: + return None + +def getDateStrings(s): + + "Parse the string 's', extracting and returning all date strings." + + start = 0 + m = date_regexp.search(s, start) + l = [] + while m: + l.append("-".join(m.groups())) + m = date_regexp.search(s, m.end()) + return l + +def getMonth(s): + + "Parse the string 's', extracting and returning a month object." + + m = month_regexp.search(s) + if m: + return Month(map(int, m.groups())) + else: + return None + +def getCurrentDate(): + + "Return the current date as a (year, month, day) tuple." + + today = datetime.date.today() + return Date((today.year, today.month, today.day)) + +def getCurrentMonth(): + + "Return the current month as a (year, month) tuple." + + today = datetime.date.today() + return Month((today.year, today.month)) + +def getCurrentYear(): + + "Return the current year." + + today = datetime.date.today() + return today.year + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r bf446fb77924 LocationSupport.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LocationSupport.py Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,165 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - LocationSupport library (derived from EventAggregatorSupport) + + @copyright: 2011, 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +import operator +import re + +__version__ = "0.1" + +location_normalised_regexp = re.compile( + ur"(?:\d+\w*\s+)?" # preceding postcode (optional) + ur"(?P" # start of group of interest + ur"\w[\w\s-]+?" # area or town + ur"(?:,(?:\s*[\w-]+)+)?" # country (optional) + ur")$", re.UNICODE) + +# Utility functions. + +def sign(x): + if x < 0: + return -1 + else: + return 1 + +# Location-related functions. + +class Reference: + + "A map reference." + + def __init__(self, degrees, minutes=0, seconds=0): + self.degrees = degrees + self.minutes = minutes + self.seconds = seconds + + def __repr__(self): + return "Reference(%d, %d, %f)" % (self.degrees, self.minutes, self.seconds) + + def __str__(self): + return "%d:%d:%f" % (self.degrees, self.minutes, self.seconds) + + def __add__(self, other): + if not isinstance(other, Reference): + return NotImplemented + else: + s = sign(self.degrees) + o = sign(other.degrees) + carry, seconds = adc(s * self.seconds, o * other.seconds) + carry, minutes = adc(s * self.minutes, o * other.minutes + carry) + return Reference(self.degrees + other.degrees + carry, minutes, seconds) + + def __sub__(self, other): + if not isinstance(other, Reference): + return NotImplemented + else: + return self.__add__(Reference(-other.degrees, other.minutes, other.seconds)) + + def _compare(self, op, other): + if not isinstance(other, Reference): + return NotImplemented + else: + return op(self.to_degrees(), other.to_degrees()) + + def __eq__(self, other): + return self._compare(operator.eq, other) + + def __ne__(self, other): + return self._compare(operator.ne, other) + + def __lt__(self, other): + return self._compare(operator.lt, other) + + def __le__(self, other): + return self._compare(operator.le, other) + + def __gt__(self, other): + return self._compare(operator.gt, other) + + def __ge__(self, other): + return self._compare(operator.ge, other) + + def to_degrees(self): + return sign(self.degrees) * (abs(self.degrees) + self.minutes / 60.0 + self.seconds / 3600.0) + + def to_pixels(self, scale): + return self.to_degrees() * scale + +def adc(x, y): + result = x + y + return divmod(result, 60) + +def getPositionForReference(latitude, longitude, map_y, map_x, map_x_scale, map_y_scale): + return (longitude - map_x).to_pixels(map_x_scale), (latitude - map_y).to_pixels(map_y_scale) + +def getPositionForCentrePoint(position, map_x_scale, map_y_scale): + x, y = position + return x - map_x_scale / 2.0, y - map_y_scale / 2.0 + +def getMapReference(value): + + "Return a map reference by parsing the given 'value'." + + if value.find(":") != -1: + return getMapReferenceFromDMS(value) + else: + return getMapReferenceFromDecimal(value) + +def getMapReferenceFromDMS(value): + + """ + Return a map reference by parsing the given 'value' expressed as degrees, + minutes, seconds. + """ + + values = value.split(":") + values = map(int, values[:2]) + map(float, values[2:3]) + return Reference(*values) + +def getMapReferenceFromDecimal(value): + + "Return a map reference by parsing the given 'value' in decimal degrees." + + value = float(value) + degrees, remainder = divmod(abs(value * 3600), 3600) + minutes, seconds = divmod(remainder, 60) + return Reference(sign(value) * degrees, minutes, seconds) + +# User interface functions. + +def getNormalisedLocation(location): + + """ + Attempt to return a normalised 'location' of the form ", " or + "". + """ + + match = location_normalised_regexp.search(location) + if match: + return match.group("location") + else: + return None + +def getLocationPosition(location, locations): + + """ + Attempt to return the position of the given 'location' using the 'locations' + dictionary provided. If no position can be found, return a latitude of None + and a longitude of None. + """ + + latitude, longitude = None, None + + if location is not None: + try: + latitude, longitude = map(getMapReference, locations[location].split()) + except (KeyError, ValueError): + pass + + return latitude, longitude + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r bf446fb77924 MoinDateSupport.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinDateSupport.py Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,163 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - MoinDateSupport library (derived from EventAggregatorSupport) + + @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinSupport import * + +__version__ = "0.1" + +# User interface functions. + +def getParameterDate(arg): + + "Interpret 'arg', recognising keywords and simple arithmetic operations." + + n = None + + if arg is None: + return None + + elif arg.startswith("current"): + date = getCurrentDate() + if len(arg) > 8: + n = int(arg[7:]) + + elif arg.startswith("yearstart"): + date = Date((getCurrentYear(), 1, 1)) + if len(arg) > 10: + n = int(arg[9:]) + + elif arg.startswith("yearend"): + date = Date((getCurrentYear(), 12, 31)) + if len(arg) > 8: + n = int(arg[7:]) + + else: + date = getDate(arg) + + if n is not None: + date = date.day_update(n) + + return date + +def getParameterMonth(arg): + + "Interpret 'arg', recognising keywords and simple arithmetic operations." + + n = None + + if arg is None: + return None + + elif arg.startswith("current"): + date = getCurrentMonth() + if len(arg) > 8: + n = int(arg[7:]) + + elif arg.startswith("yearstart"): + date = Month((getCurrentYear(), 1)) + if len(arg) > 10: + n = int(arg[9:]) + + elif arg.startswith("yearend"): + date = Month((getCurrentYear(), 12)) + if len(arg) > 8: + n = int(arg[7:]) + + else: + date = getMonth(arg) + + if n is not None: + date = date.month_update(n) + + return date + +def getFormDate(request, calendar_name, argname): + + """ + Return the date from the 'request' for the calendar with the given + 'calendar_name' using the parameter having the given 'argname'. + """ + + arg = getQualifiedParameter(request, calendar_name, argname) + return getParameterDate(arg) + +def getFormMonth(request, calendar_name, argname): + + """ + Return the month from the 'request' for the calendar with the given + 'calendar_name' using the parameter having the given 'argname'. + """ + + arg = getQualifiedParameter(request, calendar_name, argname) + return getParameterMonth(arg) + +def getFormDateTriple(request, yeararg, montharg, dayarg): + + """ + Return the date from the 'request' for the calendar with the given + 'calendar_name' using the parameters having the given 'yeararg', 'montharg' + and 'dayarg' names. + """ + + year = getParameter(request, yeararg) + month = getParameter(request, montharg) + day = getParameter(request, dayarg) + if year and month and day: + return Date((int(year), int(month), int(day))) + else: + return None + +def getFormMonthPair(request, yeararg, montharg): + + """ + Return the month from the 'request' for the calendar with the given + 'calendar_name' using the parameters having the given 'yeararg' and + 'montharg' names. + """ + + year = getParameter(request, yeararg) + month = getParameter(request, montharg) + if year and month: + return Month((int(year), int(month))) + else: + return None + +def getFullDateLabel(request, date): + + """ + Return the full month plus year label using the given 'request' and + 'year_month'. + """ + + if not date: + return "" + + _ = request.getText + year, month, day = date.as_tuple()[:3] + start_weekday, number_of_days = date.month_properties() + weekday = (start_weekday + day - 1) % 7 + day_label = _(getDayLabel(weekday)) + month_label = _(getMonthLabel(month)) + return "%s %s %s %s" % (day_label, day, month_label, year) + +def getFullMonthLabel(request, year_month): + + """ + Return the full month plus year label using the given 'request' and + 'year_month'. + """ + + if not year_month: + return "" + + _ = request.getText + year, month = year_month.as_tuple()[:2] + month_label = _(getMonthLabel(month)) + return "%s %s" % (month_label, year) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r bf446fb77924 MoinSupport.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinSupport.py Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,260 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - MoinSupport library (derived from EventAggregatorSupport) + + @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie + @copyright: 2000-2004 Juergen Hermann , + 2005-2008 MoinMoin:ThomasWaldmann. + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from DateSupport import * +from MoinMoin import wikiutil +import re +import time + +__version__ = "0.1" + +# Content type parsing. + +encoding_regexp_str = ur'(?P[^\s;]*)(?:;\s*charset=(?P[-A-Za-z0-9]+))?' +encoding_regexp = re.compile(encoding_regexp_str) + +# Utility functions. + +def getContentTypeAndEncoding(content_type): + m = encoding_regexp.search(content_type) + if m: + return m.group("content_type"), m.group("encoding") + else: + return None, None + +def int_or_none(x): + if x is None: + return x + else: + return int(x) + +# Utility classes and associated functions. + +class Form: + + """ + A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x + environment. + """ + + def __init__(self, form): + self.form = form + + def has_key(self, name): + return not not self.form.getlist(name) + + def get(self, name, default=None): + values = self.form.getlist(name) + if not values: + return default + else: + return values + + def __getitem__(self, name): + return self.form.getlist(name) + +class ActionSupport: + + """ + Work around disruptive MoinMoin changes in 1.9, and also provide useful + convenience methods. + """ + + def get_form(self): + return get_form(self.request) + + def _get_selected(self, value, input_value): + + """ + Return the HTML attribute text indicating selection of an option (or + otherwise) if 'value' matches 'input_value'. + """ + + return input_value is not None and value == input_value and 'selected="selected"' or '' + + def _get_selected_for_list(self, value, input_values): + + """ + Return the HTML attribute text indicating selection of an option (or + otherwise) if 'value' matches one of the 'input_values'. + """ + + return value in input_values and 'selected="selected"' or '' + + def _get_input(self, form, name, default=None): + + """ + Return the input from 'form' having the given 'name', returning either + the input converted to an integer or the given 'default' (optional, None + if not specified). + """ + + value = form.get(name, [None])[0] + if not value: # true if 0 obtained + return default + else: + return int(value) + +def get_form(request): + + "Work around disruptive MoinMoin changes in 1.9." + + if hasattr(request, "values"): + return Form(request.values) + else: + return request.form + +class send_headers_cls: + + """ + A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a + 1.9.x environment. + """ + + def __init__(self, request): + self.request = request + + def __call__(self, headers): + for header in headers: + parts = header.split(":") + self.request.headers.add(parts[0], ":".join(parts[1:])) + +def get_send_headers(request): + + "Return a function that can send response headers." + + if hasattr(request, "http_headers"): + return request.http_headers + elif hasattr(request, "emit_http_headers"): + return request.emit_http_headers + else: + return send_headers_cls(request) + +def escattr(s): + return wikiutil.escape(s, 1) + +def getPathInfo(request): + if hasattr(request, "getPathinfo"): + return request.getPathinfo() + else: + return request.path + +# Action support functions. + +def getPageRevision(page): + + "Return the revision details dictionary for the given 'page'." + + # From Page.edit_info... + + if hasattr(page, "editlog_entry"): + line = page.editlog_entry() + else: + line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x + + # Similar to Page.mtime_usecs behaviour... + + if line: + timestamp = line.ed_time_usecs + mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x + comment = line.comment + else: + mtime = 0 + comment = "" + + # Leave the time zone empty. + + return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + (None,)), "comment" : comment} + +# User interface functions. + +def getParameter(request, name, default=None): + + """ + Using the given 'request', return the value of the parameter with the given + 'name', returning the optional 'default' (or None) if no value was supplied + in the 'request'. + """ + + return get_form(request).get(name, [default])[0] + +def getQualifiedParameter(request, prefix, argname, default=None): + + """ + Using the given 'request', 'prefix' and 'argname', retrieve the value of the + qualified parameter, returning the optional 'default' (or None) if no value + was supplied in the 'request'. + """ + + argname = getQualifiedParameterName(prefix, argname) + return getParameter(request, argname, default) + +def getQualifiedParameterName(prefix, argname): + + """ + Return the qualified parameter name using the given 'prefix' and 'argname'. + """ + + if prefix is None: + return argname + else: + return "%s-%s" % (prefix, argname) + +# Page-related functions. + +def getPrettyPageName(page): + + "Return a nicely formatted title/name for the given 'page'." + + title = page.split_title(force=1) + return getPrettyTitle(title) + +def linkToPage(request, page, text, query_string=None): + + """ + Using 'request', return a link to 'page' with the given link 'text' and + optional 'query_string'. + """ + + text = wikiutil.escape(text) + return page.link_to_raw(request, text, query_string) + +def linkToResource(url, request, text, query_string=None): + + """ + Using 'request', return a link to 'url' with the given link 'text' and + optional 'query_string'. + """ + + if query_string: + query_string = wikiutil.makeQueryString(query_string) + url = "%s?%s" % (url, query_string) + + formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter + + output = [] + output.append(formatter.url(1, url)) + output.append(formatter.text(text)) + output.append(formatter.url(0)) + return "".join(output) + +def getFullPageName(parent, title): + + """ + Return a full page name from the given 'parent' page (can be empty or None) + and 'title' (a simple page name). + """ + + if parent: + return "%s/%s" % (parent.rstrip("/"), title) + else: + return title + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r bf446fb77924 PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PKG-INFO Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,21 @@ +Metadata-Version: 1.1 +Name: MoinSupport +Version: 0.1 +Author: Paul Boddie +Author-email: paul at boddie org uk +Maintainer: Paul Boddie +Maintainer-email: paul at boddie org uk +Home-page: http://hgweb.boddie.org.uk/MoinSupport +Download-url: http://hgweb.boddie.org.uk/MoinSupport/archive/rel-0-1.tar.bz2 +Summary: Support libraries for MoinMoin extensions +License: GPL (version 2 or later) +Description: The MoinSupport distribution provides libraries handling datetime + and location-related information, along with support for Moin 1.8/1.9 + compatibility and various user interface functions. +Keywords: MoinMoin Wiki datetime date time location +Requires: MoinMoin +Classifier: Development Status :: 3 - Alpha +Classifier: License :: OSI Approved :: GNU General Public License (GPL) +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content diff -r 000000000000 -r bf446fb77924 README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.txt Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,74 @@ +Introduction +------------ + +The MoinSupport distribution provides support libraries for use by MoinMoin +extensions. Some of the provided modules can be used independently of +MoinMoin, such as the DateSupport and LocationSupport modules which do not +themselves import any MoinMoin functionality. + +Installation +------------ + +To install the support library, consider using the moinsetup tool. See the +"Recommended Software" section below for more information. + +With moinsetup and a suitable configuration file, the installation is done as +follows with $MSDIR referring to the MoinSupport distribution directory +containing this README.txt file: + + python moinsetup.py -f moinsetup.cfg -m install_extension_package $MSDIR + +The command above uses the setup.py script provided as follows: + + python setup.py install --prefix=path-to-moin-prefix + +Recommended Software +-------------------- + +See the "Dependencies" section below for essential software. + +The moinsetup tool is recommended for installation since it aims to support +all versions of MoinMoin that are supported for use with this software. + +See the following page for information on moinsetup: + +http://moinmo.in/ScriptMarket/moinsetup + +Contact, Copyright and Licence Information +------------------------------------------ + +See the following Web pages for more information about this work: + +http://hgweb.boddie.org.uk/MoinSupport + +The author can be contacted at the following e-mail address: + +paul@boddie.org.uk + +Copyright and licence information can be found in the docs directory - see +docs/COPYING.txt and docs/LICENCE.txt for more information. + +Dependencies +------------ + +MoinSupport has the following basic dependencies: + +Packages Release Information +-------- ------------------- + +pytz Tested with 2007k (specifically 2007k-0ubuntu2) + Source: http://pytz.sourceforge.net/ + +If time zone handling is not required, pytz need not be installed. It is, +however, highly recommended that pytz be installed. + +Release Procedures +------------------ + +Update the __version__ attributes in the modules and the setup.py version +details. +Change the version number and package filename/directory in the documentation. +Update the setup.py and PKG-INFO files. +Update the release notes (see above). +Tag, export. +Archive, upload. diff -r 000000000000 -r bf446fb77924 docs/COPYING.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/COPYING.txt Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,26 @@ +Licence Agreement +----------------- + +Copyright (C) 2008, 2009, 2010, 2011, 2012 Paul Boddie + +Some pieces of MoinMoin code were used in this work - typically +pieces which demonstrate how to perform certain common tasks - +and are thus covered by the following copyrights: + +Copyright (C) 2000-2004 Juergen Hermann +Copyright (C) 2005-2008 MoinMoin:ThomasWaldmann + +This software 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 Foundation; either version 2 of +the License, or (at your option) any later version. + +This software 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 General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this library; see the file LICENCE.txt +If not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA diff -r 000000000000 -r bf446fb77924 docs/LICENCE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/LICENCE.txt Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r 000000000000 -r bf446fb77924 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Sun Jan 22 00:04:16 2012 +0100 @@ -0,0 +1,13 @@ +#! /usr/bin/env python + +from distutils.core import setup + +setup( + name = "MoinSupport", + description = "Support libraries for MoinMoin extensions", + author = "Paul Boddie", + author_email = "paul@boddie.org.uk", + url = "http://hgweb.boddie.org.uk/MoinSupport", + version = "0.1", + py_modules = ["DateSupport", "LocationSupport", "MoinDateSupport", "MoinSupport"] + )