1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/DateSupport.py Sun Jan 22 00:04:16 2012 +0100
1.3 @@ -0,0 +1,897 @@
1.4 +# -*- coding: iso-8859-1 -*-
1.5 +"""
1.6 + MoinMoin - DateSupport library (derived from EventAggregatorSupport)
1.7 +
1.8 + @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk>
1.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.10 +"""
1.11 +
1.12 +import calendar
1.13 +import datetime
1.14 +import re
1.15 +import bisect
1.16 +
1.17 +try:
1.18 + import pytz
1.19 +except ImportError:
1.20 + pytz = None
1.21 +
1.22 +__version__ = "0.1"
1.23 +
1.24 +# Date labels.
1.25 +
1.26 +month_labels = ["January", "February", "March", "April", "May", "June",
1.27 + "July", "August", "September", "October", "November", "December"]
1.28 +weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
1.29 +
1.30 +# Month, date, time and datetime parsing.
1.31 +
1.32 +month_regexp_str = ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})'
1.33 +date_regexp_str = ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
1.34 +time_regexp_str = ur'(?P<hour>[0-2][0-9]):(?P<minute>[0-5][0-9])(?::(?P<second>[0-6][0-9]))?'
1.35 +timezone_offset_str = ur'(?P<offset>(UTC)?(?:(?P<sign>[-+])(?P<hours>[0-9]{2})(?::?(?P<minutes>[0-9]{2}))?))'
1.36 +timezone_olson_str = ur'(?P<olson>[a-zA-Z]+(?:/[-_a-zA-Z]+){1,2})'
1.37 +timezone_utc_str = ur'UTC'
1.38 +timezone_regexp_str = ur'(?P<zone>' + timezone_offset_str + '|' + timezone_olson_str + '|' + timezone_utc_str + ')'
1.39 +datetime_regexp_str = date_regexp_str + ur'(?:\s+' + time_regexp_str + ur'(?:\s+' + timezone_regexp_str + ur')?)?'
1.40 +
1.41 +month_regexp = re.compile(month_regexp_str, re.UNICODE)
1.42 +date_regexp = re.compile(date_regexp_str, re.UNICODE)
1.43 +time_regexp = re.compile(time_regexp_str, re.UNICODE)
1.44 +timezone_olson_regexp = re.compile(timezone_olson_str, re.UNICODE)
1.45 +timezone_offset_regexp = re.compile(timezone_offset_str, re.UNICODE)
1.46 +datetime_regexp = re.compile(datetime_regexp_str, re.UNICODE)
1.47 +
1.48 +# iCalendar date and datetime parsing.
1.49 +
1.50 +date_icalendar_regexp_str = ur'(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})'
1.51 +datetime_icalendar_regexp_str = date_icalendar_regexp_str + \
1.52 + ur'(?:' \
1.53 + ur'T(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-6][0-9])' \
1.54 + ur'(?P<utc>Z)?' \
1.55 + ur')?'
1.56 +
1.57 +date_icalendar_regexp = re.compile(date_icalendar_regexp_str, re.UNICODE)
1.58 +datetime_icalendar_regexp = re.compile(datetime_icalendar_regexp_str, re.UNICODE)
1.59 +
1.60 +# Utility functions.
1.61 +
1.62 +def int_or_none(x):
1.63 + if x is None:
1.64 + return x
1.65 + else:
1.66 + return int(x)
1.67 +
1.68 +def getMonthLabel(month):
1.69 +
1.70 + "Return an unlocalised label for the given 'month'."
1.71 +
1.72 + return month_labels[month - 1] # zero-based labels
1.73 +
1.74 +def getDayLabel(weekday):
1.75 +
1.76 + "Return an unlocalised label for the given 'weekday'."
1.77 +
1.78 + return weekday_labels[weekday]
1.79 +
1.80 +# Interfaces.
1.81 +
1.82 +class ActsAsTimespan:
1.83 + pass
1.84 +
1.85 +# Date-related functions.
1.86 +
1.87 +def cmp_dates_as_day_start(a, b):
1.88 +
1.89 + """
1.90 + Compare dates/datetimes 'a' and 'b' treating dates without time information
1.91 + as the earliest time in a particular day.
1.92 + """
1.93 +
1.94 + are_equal = a == b
1.95 +
1.96 + if are_equal:
1.97 + a2 = a.as_datetime_or_date()
1.98 + b2 = b.as_datetime_or_date()
1.99 +
1.100 + if isinstance(a2, Date) and isinstance(b2, DateTime):
1.101 + return -1
1.102 + elif isinstance(a2, DateTime) and isinstance(b2, Date):
1.103 + return 1
1.104 +
1.105 + return cmp(a, b)
1.106 +
1.107 +class Convertible:
1.108 +
1.109 + "Support for converting temporal objects."
1.110 +
1.111 + def _get_converter(self, resolution):
1.112 + if resolution == "month":
1.113 + return lambda x: x and x.as_month()
1.114 + elif resolution == "date":
1.115 + return lambda x: x and x.as_date()
1.116 + elif resolution == "datetime":
1.117 + return lambda x: x and x.as_datetime_or_date()
1.118 + else:
1.119 + return lambda x: x
1.120 +
1.121 +class Temporal(Convertible):
1.122 +
1.123 + "A simple temporal representation, common to dates and times."
1.124 +
1.125 + def __init__(self, data):
1.126 + self.data = list(data)
1.127 +
1.128 + def __repr__(self):
1.129 + return "%s(%r)" % (self.__class__.__name__, self.data)
1.130 +
1.131 + def __hash__(self):
1.132 + return hash(self.as_tuple())
1.133 +
1.134 + def as_tuple(self):
1.135 + return tuple(self.data)
1.136 +
1.137 + def convert(self, resolution):
1.138 + return self._get_converter(resolution)(self)
1.139 +
1.140 + def __cmp__(self, other):
1.141 +
1.142 + """
1.143 + The result of comparing this instance with 'other' is derived from a
1.144 + comparison of the instances' date(time) data at the highest common
1.145 + resolution, meaning that if a date is compared to a datetime, the
1.146 + datetime will be considered as a date. Thus, a date and a datetime
1.147 + referring to the same date will be considered equal.
1.148 + """
1.149 +
1.150 + if not isinstance(other, Temporal):
1.151 + return NotImplemented
1.152 + else:
1.153 + data = self.as_tuple()
1.154 + other_data = other.as_tuple()
1.155 + length = min(len(data), len(other_data))
1.156 + return cmp(data[:length], other_data[:length])
1.157 +
1.158 + def __sub__(self, other):
1.159 +
1.160 + """
1.161 + Return the difference between this object and the 'other' object at the
1.162 + highest common accuracy of both objects.
1.163 + """
1.164 +
1.165 + if not isinstance(other, Temporal):
1.166 + return NotImplemented
1.167 + else:
1.168 + data = self.as_tuple()
1.169 + other_data = other.as_tuple()
1.170 + if len(data) < len(other_data):
1.171 + return len(self.until(other))
1.172 + else:
1.173 + return len(other.until(self))
1.174 +
1.175 + def _until(self, start, end, nextfn, prevfn):
1.176 +
1.177 + """
1.178 + Return a collection of units of time by starting from the given 'start'
1.179 + and stepping across intervening units until 'end' is reached, using the
1.180 + given 'nextfn' and 'prevfn' to step from one unit to the next.
1.181 + """
1.182 +
1.183 + current = start
1.184 + units = [current]
1.185 + if current < end:
1.186 + while current < end:
1.187 + current = nextfn(current)
1.188 + units.append(current)
1.189 + elif current > end:
1.190 + while current > end:
1.191 + current = prevfn(current)
1.192 + units.append(current)
1.193 + return units
1.194 +
1.195 + def ambiguous(self):
1.196 +
1.197 + "Only times can be ambiguous."
1.198 +
1.199 + return 0
1.200 +
1.201 +class Month(Temporal):
1.202 +
1.203 + "A simple year-month representation."
1.204 +
1.205 + def __str__(self):
1.206 + return "%04d-%02d" % self.as_tuple()[:2]
1.207 +
1.208 + def as_datetime(self, day, hour, minute, second, zone):
1.209 + return DateTime(self.as_tuple() + (day, hour, minute, second, zone))
1.210 +
1.211 + def as_date(self, day):
1.212 + if day < 0:
1.213 + weekday, ndays = self.month_properties()
1.214 + day = ndays + 1 + day
1.215 + return Date(self.as_tuple() + (day,))
1.216 +
1.217 + def as_month(self):
1.218 + return self
1.219 +
1.220 + def year(self):
1.221 + return self.data[0]
1.222 +
1.223 + def month(self):
1.224 + return self.data[1]
1.225 +
1.226 + def month_properties(self):
1.227 +
1.228 + """
1.229 + Return the weekday of the 1st of the month, along with the number of
1.230 + days, as a tuple.
1.231 + """
1.232 +
1.233 + year, month = self.as_tuple()[:2]
1.234 + return calendar.monthrange(year, month)
1.235 +
1.236 + def month_update(self, n=1):
1.237 +
1.238 + "Return the month updated by 'n' months."
1.239 +
1.240 + year, month = self.as_tuple()[:2]
1.241 + return Month((year + (month - 1 + n) / 12, (month - 1 + n) % 12 + 1))
1.242 +
1.243 + update = month_update
1.244 +
1.245 + def next_month(self):
1.246 +
1.247 + "Return the month following this one."
1.248 +
1.249 + return self.month_update(1)
1.250 +
1.251 + next = next_month
1.252 +
1.253 + def previous_month(self):
1.254 +
1.255 + "Return the month preceding this one."
1.256 +
1.257 + return self.month_update(-1)
1.258 +
1.259 + previous = previous_month
1.260 +
1.261 + def months_until(self, end):
1.262 +
1.263 + "Return the collection of months from this month until 'end'."
1.264 +
1.265 + return self._until(self.as_month(), end.as_month(), Month.next_month, Month.previous_month)
1.266 +
1.267 + until = months_until
1.268 +
1.269 +class Date(Month):
1.270 +
1.271 + "A simple year-month-day representation."
1.272 +
1.273 + def constrain(self):
1.274 + year, month, day = self.as_tuple()[:3]
1.275 +
1.276 + month = max(min(month, 12), 1)
1.277 + wd, last_day = calendar.monthrange(year, month)
1.278 + day = max(min(day, last_day), 1)
1.279 +
1.280 + self.data[1:3] = month, day
1.281 +
1.282 + def __str__(self):
1.283 + return "%04d-%02d-%02d" % self.as_tuple()[:3]
1.284 +
1.285 + def as_datetime(self, hour, minute, second, zone):
1.286 + return DateTime(self.as_tuple() + (hour, minute, second, zone))
1.287 +
1.288 + def as_start_of_day(self):
1.289 + return self.as_datetime(None, None, None, None)
1.290 +
1.291 + def as_date(self):
1.292 + return self
1.293 +
1.294 + def as_datetime_or_date(self):
1.295 + return self
1.296 +
1.297 + def as_month(self):
1.298 + return Month(self.data[:2])
1.299 +
1.300 + def day(self):
1.301 + return self.data[2]
1.302 +
1.303 + def day_update(self, n=1):
1.304 +
1.305 + "Return the month updated by 'n' days."
1.306 +
1.307 + delta = datetime.timedelta(n)
1.308 + dt = datetime.date(*self.as_tuple()[:3])
1.309 + dt_new = dt + delta
1.310 + return Date((dt_new.year, dt_new.month, dt_new.day))
1.311 +
1.312 + update = day_update
1.313 +
1.314 + def next_day(self):
1.315 +
1.316 + "Return the date following this one."
1.317 +
1.318 + year, month, day = self.as_tuple()[:3]
1.319 + _wd, end_day = calendar.monthrange(year, month)
1.320 + if day == end_day:
1.321 + if month == 12:
1.322 + return Date((year + 1, 1, 1))
1.323 + else:
1.324 + return Date((year, month + 1, 1))
1.325 + else:
1.326 + return Date((year, month, day + 1))
1.327 +
1.328 + next = next_day
1.329 +
1.330 + def previous_day(self):
1.331 +
1.332 + "Return the date preceding this one."
1.333 +
1.334 + year, month, day = self.as_tuple()[:3]
1.335 + if day == 1:
1.336 + if month == 1:
1.337 + return Date((year - 1, 12, 31))
1.338 + else:
1.339 + _wd, end_day = calendar.monthrange(year, month - 1)
1.340 + return Date((year, month - 1, end_day))
1.341 + else:
1.342 + return Date((year, month, day - 1))
1.343 +
1.344 + previous = previous_day
1.345 +
1.346 + def days_until(self, end):
1.347 +
1.348 + "Return the collection of days from this date until 'end'."
1.349 +
1.350 + return self._until(self.as_date(), end.as_date(), Date.next_day, Date.previous_day)
1.351 +
1.352 + until = days_until
1.353 +
1.354 +class DateTime(Date):
1.355 +
1.356 + "A simple date plus time representation."
1.357 +
1.358 + def constrain(self):
1.359 + Date.constrain(self)
1.360 +
1.361 + hour, minute, second = self.as_tuple()[3:6]
1.362 +
1.363 + if self.has_time():
1.364 + hour = max(min(hour, 23), 0)
1.365 + minute = max(min(minute, 59), 0)
1.366 +
1.367 + if second is not None:
1.368 + second = max(min(second, 60), 0) # support leap seconds
1.369 +
1.370 + self.data[3:6] = hour, minute, second
1.371 +
1.372 + def __str__(self):
1.373 + return Date.__str__(self) + self.time_string()
1.374 +
1.375 + def time_string(self):
1.376 + if self.has_time():
1.377 + data = self.as_tuple()
1.378 + time_str = " %02d:%02d" % data[3:5]
1.379 + if data[5] is not None:
1.380 + time_str += ":%02d" % data[5]
1.381 + if data[6] is not None:
1.382 + time_str += " %s" % data[6]
1.383 + return time_str
1.384 + else:
1.385 + return ""
1.386 +
1.387 + def as_HTTP_datetime_string(self):
1.388 + weekday = calendar.weekday(*self.data[:3])
1.389 + return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ((
1.390 + getDayLabel(weekday),
1.391 + self.data[2],
1.392 + getMonthLabel(self.data[1]),
1.393 + self.data[0]
1.394 + ) + tuple(self.data[3:6]))
1.395 +
1.396 + def as_datetime(self):
1.397 + return self
1.398 +
1.399 + def as_date(self):
1.400 + return Date(self.data[:3])
1.401 +
1.402 + def as_datetime_or_date(self):
1.403 +
1.404 + """
1.405 + Return a date for this datetime if fields are missing. Otherwise, return
1.406 + this datetime itself.
1.407 + """
1.408 +
1.409 + if not self.has_time():
1.410 + return self.as_date()
1.411 + else:
1.412 + return self
1.413 +
1.414 + def __cmp__(self, other):
1.415 +
1.416 + """
1.417 + The result of comparing this instance with 'other' is, if both instances
1.418 + are datetime instances, derived from a comparison of the datetimes
1.419 + converted to UTC. If one or both datetimes cannot be converted to UTC,
1.420 + the datetimes are compared using the basic temporal comparison which
1.421 + compares their raw time data.
1.422 + """
1.423 +
1.424 + this = self
1.425 +
1.426 + if this.has_time():
1.427 + if isinstance(other, DateTime):
1.428 + if other.has_time():
1.429 + this_utc = this.to_utc()
1.430 + other_utc = other.to_utc()
1.431 + if this_utc is not None and other_utc is not None:
1.432 + return cmp(this_utc.as_tuple(), other_utc.as_tuple())
1.433 + else:
1.434 + other = other.padded()
1.435 + else:
1.436 + this = this.padded()
1.437 +
1.438 + return Date.__cmp__(this, other)
1.439 +
1.440 + def has_time(self):
1.441 +
1.442 + """
1.443 + Return whether this object has any time information. Objects without
1.444 + time information can refer to the very start of a day.
1.445 + """
1.446 +
1.447 + return self.data[3] is not None and self.data[4] is not None
1.448 +
1.449 + def time(self):
1.450 + return self.data[3:]
1.451 +
1.452 + def seconds(self):
1.453 + return self.data[5]
1.454 +
1.455 + def time_zone(self):
1.456 + return self.data[6]
1.457 +
1.458 + def set_time_zone(self, value):
1.459 + self.data[6] = value
1.460 +
1.461 + def padded(self, empty_value=0):
1.462 +
1.463 + """
1.464 + Return a datetime with missing fields defined as being the given
1.465 + 'empty_value' or 0 if not specified.
1.466 + """
1.467 +
1.468 + data = []
1.469 + for x in self.data[:6]:
1.470 + if x is None:
1.471 + data.append(empty_value)
1.472 + else:
1.473 + data.append(x)
1.474 +
1.475 + data += self.data[6:]
1.476 + return DateTime(data)
1.477 +
1.478 + def to_utc(self):
1.479 +
1.480 + """
1.481 + Return this object converted to UTC, or None if such a conversion is not
1.482 + defined.
1.483 + """
1.484 +
1.485 + if not self.has_time():
1.486 + return None
1.487 +
1.488 + offset = self.utc_offset()
1.489 + if offset:
1.490 + hours, minutes = offset
1.491 +
1.492 + # Invert the offset to get the correction.
1.493 +
1.494 + hours, minutes = -hours, -minutes
1.495 +
1.496 + # Get the components.
1.497 +
1.498 + hour, minute, second, zone = self.time()
1.499 + date = self.as_date()
1.500 +
1.501 + # Add the minutes and hours.
1.502 +
1.503 + minute += minutes
1.504 + if minute < 0 or minute > 59:
1.505 + hour += minute / 60
1.506 + minute = minute % 60
1.507 +
1.508 + # NOTE: This makes various assumptions and probably would not work
1.509 + # NOTE: for general arithmetic.
1.510 +
1.511 + hour += hours
1.512 + if hour < 0:
1.513 + date = date.previous_day()
1.514 + hour += 24
1.515 + elif hour > 23:
1.516 + date = date.next_day()
1.517 + hour -= 24
1.518 +
1.519 + return date.as_datetime(hour, minute, second, "UTC")
1.520 +
1.521 + # Cannot convert.
1.522 +
1.523 + else:
1.524 + return None
1.525 +
1.526 + def utc_offset(self):
1.527 +
1.528 + "Return the UTC offset in hours and minutes."
1.529 +
1.530 + zone = self.time_zone()
1.531 + if not zone:
1.532 + return None
1.533 +
1.534 + # Support explicit UTC zones.
1.535 +
1.536 + if zone == "UTC":
1.537 + return 0, 0
1.538 +
1.539 + # Attempt to return a UTC offset where an explicit offset has been set.
1.540 +
1.541 + match = timezone_offset_regexp.match(zone)
1.542 + if match:
1.543 + if match.group("sign") == "-":
1.544 + sign = -1
1.545 + else:
1.546 + sign = 1
1.547 +
1.548 + hours = int(match.group("hours")) * sign
1.549 + minutes = int(match.group("minutes") or 0) * sign
1.550 + return hours, minutes
1.551 +
1.552 + # Attempt to handle Olson time zone identifiers.
1.553 +
1.554 + dt = self.as_olson_datetime()
1.555 + if dt:
1.556 + seconds = dt.utcoffset().seconds
1.557 + hours = seconds / 3600
1.558 + minutes = (seconds % 3600) / 60
1.559 + return hours, minutes
1.560 +
1.561 + # Otherwise return None.
1.562 +
1.563 + return None
1.564 +
1.565 + def olson_identifier(self):
1.566 +
1.567 + "Return the Olson identifier from any zone information."
1.568 +
1.569 + zone = self.time_zone()
1.570 + if not zone:
1.571 + return None
1.572 +
1.573 + # Attempt to match an identifier.
1.574 +
1.575 + match = timezone_olson_regexp.match(zone)
1.576 + if match:
1.577 + return match.group("olson")
1.578 + else:
1.579 + return None
1.580 +
1.581 + def _as_olson_datetime(self, hours=None):
1.582 +
1.583 + """
1.584 + Return a Python datetime object for this datetime interpreted using any
1.585 + Olson time zone identifier and the given 'hours' offset, raising one of
1.586 + the pytz exceptions in case of ambiguity.
1.587 + """
1.588 +
1.589 + olson = self.olson_identifier()
1.590 + if olson and pytz:
1.591 + tz = pytz.timezone(olson)
1.592 + data = self.padded().as_tuple()[:6]
1.593 + dt = datetime.datetime(*data)
1.594 +
1.595 + # With an hours offset, find a time probably in a previously
1.596 + # applicable time zone.
1.597 +
1.598 + if hours is not None:
1.599 + td = datetime.timedelta(0, hours * 3600)
1.600 + dt += td
1.601 +
1.602 + ldt = tz.localize(dt, None)
1.603 +
1.604 + # With an hours offset, adjust the time to define it within the
1.605 + # previously applicable time zone but at the presumably intended
1.606 + # position.
1.607 +
1.608 + if hours is not None:
1.609 + ldt -= td
1.610 +
1.611 + return ldt
1.612 + else:
1.613 + return None
1.614 +
1.615 + def as_olson_datetime(self):
1.616 +
1.617 + """
1.618 + Return a Python datetime object for this datetime interpreted using any
1.619 + Olson time zone identifier, choosing the time from the zone before the
1.620 + period of ambiguity.
1.621 + """
1.622 +
1.623 + try:
1.624 + return self._as_olson_datetime()
1.625 + except (pytz.UnknownTimeZoneError, pytz.AmbiguousTimeError):
1.626 +
1.627 + # Try again, using an earlier local time and then stepping forward
1.628 + # in the chosen zone.
1.629 + # NOTE: Four hours earlier seems reasonable.
1.630 +
1.631 + return self._as_olson_datetime(-4)
1.632 +
1.633 + def ambiguous(self):
1.634 +
1.635 + "Return whether the time is local and ambiguous."
1.636 +
1.637 + try:
1.638 + self._as_olson_datetime()
1.639 + except (pytz.UnknownTimeZoneError, pytz.AmbiguousTimeError):
1.640 + return 1
1.641 +
1.642 + return 0
1.643 +
1.644 +class Timespan(ActsAsTimespan, Convertible):
1.645 +
1.646 + """
1.647 + A period of time which can be compared against others to check for overlaps.
1.648 + """
1.649 +
1.650 + def __init__(self, start, end):
1.651 + self.start = start
1.652 + self.end = end
1.653 +
1.654 + # NOTE: Should perhaps catch ambiguous time problems elsewhere.
1.655 +
1.656 + if self.ambiguous() and self.start is not None and self.end is not None and start > end:
1.657 + self.start, self.end = end, start
1.658 +
1.659 + def __repr__(self):
1.660 + return "%s(%r, %r)" % (self.__class__.__name__, self.start, self.end)
1.661 +
1.662 + def __hash__(self):
1.663 + return hash((self.start, self.end))
1.664 +
1.665 + def as_timespan(self):
1.666 + return self
1.667 +
1.668 + def as_limits(self):
1.669 + return self.start, self.end
1.670 +
1.671 + def ambiguous(self):
1.672 + return self.start is not None and self.start.ambiguous() or self.end is not None and self.end.ambiguous()
1.673 +
1.674 + def convert(self, resolution):
1.675 + return Timespan(*map(self._get_converter(resolution), self.as_limits()))
1.676 +
1.677 + def is_before(self, a, b):
1.678 +
1.679 + """
1.680 + Return whether 'a' is before 'b'. Since the end datetime of one period
1.681 + may be the same as the start datetime of another period, and yet the
1.682 + first period is intended to be concluded by the end datetime and not
1.683 + overlap with the other period, a different test is employed for datetime
1.684 + comparisons.
1.685 + """
1.686 +
1.687 + # Datetimes without times can be equal to dates and be considered as
1.688 + # occurring before those dates. Generally, datetimes should not be
1.689 + # produced without time information as getDateTime converts such
1.690 + # datetimes to dates.
1.691 +
1.692 + if isinstance(a, DateTime) and (isinstance(b, DateTime) or not a.has_time()):
1.693 + return a <= b
1.694 + else:
1.695 + return a < b
1.696 +
1.697 + def __contains__(self, other):
1.698 +
1.699 + """
1.700 + This instance is considered to contain 'other' if one is not before or
1.701 + after the other. If this instance overlaps or coincides with 'other',
1.702 + then 'other' is regarded as belonging to this instance's time period.
1.703 + """
1.704 +
1.705 + return self == other
1.706 +
1.707 + def __cmp__(self, other):
1.708 +
1.709 + """
1.710 + Return whether this timespan occupies the same period of time as the
1.711 + 'other'. Timespans are considered less than others if their end points
1.712 + precede the other's start point, and are considered greater than others
1.713 + if their start points follow the other's end point.
1.714 + """
1.715 +
1.716 + if isinstance(other, ActsAsTimespan):
1.717 + other = other.as_timespan()
1.718 +
1.719 + if self.end is not None and other.start is not None and self.is_before(self.end, other.start):
1.720 + return -1
1.721 + elif self.start is not None and other.end is not None and self.is_before(other.end, self.start):
1.722 + return 1
1.723 + else:
1.724 + return 0
1.725 +
1.726 + else:
1.727 + if self.end is not None and self.is_before(self.end, other):
1.728 + return -1
1.729 + elif self.start is not None and self.is_before(other, self.start):
1.730 + return 1
1.731 + else:
1.732 + return 0
1.733 +
1.734 +class TimespanCollection:
1.735 +
1.736 + """
1.737 + A class providing a list-like interface supporting membership tests at a
1.738 + particular resolution in order to maintain a collection of non-overlapping
1.739 + timespans.
1.740 + """
1.741 +
1.742 + def __init__(self, resolution, values=None):
1.743 + self.resolution = resolution
1.744 + self.values = values or []
1.745 +
1.746 + def as_timespan(self):
1.747 + return Timespan(*self.as_limits())
1.748 +
1.749 + def as_limits(self):
1.750 +
1.751 + "Return the earliest and latest points in time for this collection."
1.752 +
1.753 + if not self.values:
1.754 + return None, None
1.755 + else:
1.756 + first, last = self.values[0], self.values[-1]
1.757 + if isinstance(first, ActsAsTimespan):
1.758 + first = first.as_timespan().start
1.759 + if isinstance(last, ActsAsTimespan):
1.760 + last = last.as_timespan().end
1.761 + return first, last
1.762 +
1.763 + def convert(self, value):
1.764 + if isinstance(value, ActsAsTimespan):
1.765 + ts = value.as_timespan()
1.766 + return ts and ts.convert(self.resolution)
1.767 + else:
1.768 + return value.convert(self.resolution)
1.769 +
1.770 + def __iter__(self):
1.771 + return iter(self.values)
1.772 +
1.773 + def __len__(self):
1.774 + return len(self.values)
1.775 +
1.776 + def __getitem__(self, i):
1.777 + return self.values[i]
1.778 +
1.779 + def __setitem__(self, i, value):
1.780 + self.values[i] = value
1.781 +
1.782 + def __contains__(self, value):
1.783 + test_value = self.convert(value)
1.784 + return test_value in self.values
1.785 +
1.786 + def append(self, value):
1.787 + self.values.append(value)
1.788 +
1.789 + def insert(self, i, value):
1.790 + self.values.insert(i, value)
1.791 +
1.792 + def pop(self):
1.793 + return self.values.pop()
1.794 +
1.795 + def insert_in_order(self, value):
1.796 + bisect.insort_left(self, value)
1.797 +
1.798 +def getDate(s):
1.799 +
1.800 + "Parse the string 's', extracting and returning a date object."
1.801 +
1.802 + dt = getDateTime(s)
1.803 + if dt is not None:
1.804 + return dt.as_date()
1.805 + else:
1.806 + return None
1.807 +
1.808 +def getDateTime(s):
1.809 +
1.810 + """
1.811 + Parse the string 's', extracting and returning a datetime object where time
1.812 + information has been given or a date object where time information is
1.813 + absent.
1.814 + """
1.815 +
1.816 + m = datetime_regexp.search(s)
1.817 + if m:
1.818 + groups = list(m.groups())
1.819 +
1.820 + # Convert date and time data to integer or None.
1.821 +
1.822 + return DateTime(map(int_or_none, groups[:6]) + [m.group("zone")]).as_datetime_or_date()
1.823 + else:
1.824 + return None
1.825 +
1.826 +def getDateFromCalendar(s):
1.827 +
1.828 + """
1.829 + Parse the iCalendar format string 's', extracting and returning a date
1.830 + object.
1.831 + """
1.832 +
1.833 + dt = getDateTimeFromCalendar(s)
1.834 + if dt is not None:
1.835 + return dt.as_date()
1.836 + else:
1.837 + return None
1.838 +
1.839 +def getDateTimeFromCalendar(s):
1.840 +
1.841 + """
1.842 + Parse the iCalendar format datetime string 's', extracting and returning a
1.843 + datetime object where time information has been given or a date object where
1.844 + time information is absent.
1.845 + """
1.846 +
1.847 + m = datetime_icalendar_regexp.search(s)
1.848 + if m:
1.849 + groups = list(m.groups())
1.850 +
1.851 + # Convert date and time data to integer or None.
1.852 +
1.853 + return DateTime(map(int_or_none, groups[:6]) + [m.group("utc") and "UTC" or None]).as_datetime_or_date()
1.854 + else:
1.855 + return None
1.856 +
1.857 +def getDateStrings(s):
1.858 +
1.859 + "Parse the string 's', extracting and returning all date strings."
1.860 +
1.861 + start = 0
1.862 + m = date_regexp.search(s, start)
1.863 + l = []
1.864 + while m:
1.865 + l.append("-".join(m.groups()))
1.866 + m = date_regexp.search(s, m.end())
1.867 + return l
1.868 +
1.869 +def getMonth(s):
1.870 +
1.871 + "Parse the string 's', extracting and returning a month object."
1.872 +
1.873 + m = month_regexp.search(s)
1.874 + if m:
1.875 + return Month(map(int, m.groups()))
1.876 + else:
1.877 + return None
1.878 +
1.879 +def getCurrentDate():
1.880 +
1.881 + "Return the current date as a (year, month, day) tuple."
1.882 +
1.883 + today = datetime.date.today()
1.884 + return Date((today.year, today.month, today.day))
1.885 +
1.886 +def getCurrentMonth():
1.887 +
1.888 + "Return the current month as a (year, month) tuple."
1.889 +
1.890 + today = datetime.date.today()
1.891 + return Month((today.year, today.month))
1.892 +
1.893 +def getCurrentYear():
1.894 +
1.895 + "Return the current year."
1.896 +
1.897 + today = datetime.date.today()
1.898 + return today.year
1.899 +
1.900 +# vim: tabstop=4 expandtab shiftwidth=4
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/LocationSupport.py Sun Jan 22 00:04:16 2012 +0100
2.3 @@ -0,0 +1,165 @@
2.4 +# -*- coding: iso-8859-1 -*-
2.5 +"""
2.6 + MoinMoin - LocationSupport library (derived from EventAggregatorSupport)
2.7 +
2.8 + @copyright: 2011, 2012 by Paul Boddie <paul@boddie.org.uk>
2.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
2.10 +"""
2.11 +
2.12 +import operator
2.13 +import re
2.14 +
2.15 +__version__ = "0.1"
2.16 +
2.17 +location_normalised_regexp = re.compile(
2.18 + ur"(?:\d+\w*\s+)?" # preceding postcode (optional)
2.19 + ur"(?P<location>" # start of group of interest
2.20 + ur"\w[\w\s-]+?" # area or town
2.21 + ur"(?:,(?:\s*[\w-]+)+)?" # country (optional)
2.22 + ur")$", re.UNICODE)
2.23 +
2.24 +# Utility functions.
2.25 +
2.26 +def sign(x):
2.27 + if x < 0:
2.28 + return -1
2.29 + else:
2.30 + return 1
2.31 +
2.32 +# Location-related functions.
2.33 +
2.34 +class Reference:
2.35 +
2.36 + "A map reference."
2.37 +
2.38 + def __init__(self, degrees, minutes=0, seconds=0):
2.39 + self.degrees = degrees
2.40 + self.minutes = minutes
2.41 + self.seconds = seconds
2.42 +
2.43 + def __repr__(self):
2.44 + return "Reference(%d, %d, %f)" % (self.degrees, self.minutes, self.seconds)
2.45 +
2.46 + def __str__(self):
2.47 + return "%d:%d:%f" % (self.degrees, self.minutes, self.seconds)
2.48 +
2.49 + def __add__(self, other):
2.50 + if not isinstance(other, Reference):
2.51 + return NotImplemented
2.52 + else:
2.53 + s = sign(self.degrees)
2.54 + o = sign(other.degrees)
2.55 + carry, seconds = adc(s * self.seconds, o * other.seconds)
2.56 + carry, minutes = adc(s * self.minutes, o * other.minutes + carry)
2.57 + return Reference(self.degrees + other.degrees + carry, minutes, seconds)
2.58 +
2.59 + def __sub__(self, other):
2.60 + if not isinstance(other, Reference):
2.61 + return NotImplemented
2.62 + else:
2.63 + return self.__add__(Reference(-other.degrees, other.minutes, other.seconds))
2.64 +
2.65 + def _compare(self, op, other):
2.66 + if not isinstance(other, Reference):
2.67 + return NotImplemented
2.68 + else:
2.69 + return op(self.to_degrees(), other.to_degrees())
2.70 +
2.71 + def __eq__(self, other):
2.72 + return self._compare(operator.eq, other)
2.73 +
2.74 + def __ne__(self, other):
2.75 + return self._compare(operator.ne, other)
2.76 +
2.77 + def __lt__(self, other):
2.78 + return self._compare(operator.lt, other)
2.79 +
2.80 + def __le__(self, other):
2.81 + return self._compare(operator.le, other)
2.82 +
2.83 + def __gt__(self, other):
2.84 + return self._compare(operator.gt, other)
2.85 +
2.86 + def __ge__(self, other):
2.87 + return self._compare(operator.ge, other)
2.88 +
2.89 + def to_degrees(self):
2.90 + return sign(self.degrees) * (abs(self.degrees) + self.minutes / 60.0 + self.seconds / 3600.0)
2.91 +
2.92 + def to_pixels(self, scale):
2.93 + return self.to_degrees() * scale
2.94 +
2.95 +def adc(x, y):
2.96 + result = x + y
2.97 + return divmod(result, 60)
2.98 +
2.99 +def getPositionForReference(latitude, longitude, map_y, map_x, map_x_scale, map_y_scale):
2.100 + return (longitude - map_x).to_pixels(map_x_scale), (latitude - map_y).to_pixels(map_y_scale)
2.101 +
2.102 +def getPositionForCentrePoint(position, map_x_scale, map_y_scale):
2.103 + x, y = position
2.104 + return x - map_x_scale / 2.0, y - map_y_scale / 2.0
2.105 +
2.106 +def getMapReference(value):
2.107 +
2.108 + "Return a map reference by parsing the given 'value'."
2.109 +
2.110 + if value.find(":") != -1:
2.111 + return getMapReferenceFromDMS(value)
2.112 + else:
2.113 + return getMapReferenceFromDecimal(value)
2.114 +
2.115 +def getMapReferenceFromDMS(value):
2.116 +
2.117 + """
2.118 + Return a map reference by parsing the given 'value' expressed as degrees,
2.119 + minutes, seconds.
2.120 + """
2.121 +
2.122 + values = value.split(":")
2.123 + values = map(int, values[:2]) + map(float, values[2:3])
2.124 + return Reference(*values)
2.125 +
2.126 +def getMapReferenceFromDecimal(value):
2.127 +
2.128 + "Return a map reference by parsing the given 'value' in decimal degrees."
2.129 +
2.130 + value = float(value)
2.131 + degrees, remainder = divmod(abs(value * 3600), 3600)
2.132 + minutes, seconds = divmod(remainder, 60)
2.133 + return Reference(sign(value) * degrees, minutes, seconds)
2.134 +
2.135 +# User interface functions.
2.136 +
2.137 +def getNormalisedLocation(location):
2.138 +
2.139 + """
2.140 + Attempt to return a normalised 'location' of the form "<town>, <country>" or
2.141 + "<town>".
2.142 + """
2.143 +
2.144 + match = location_normalised_regexp.search(location)
2.145 + if match:
2.146 + return match.group("location")
2.147 + else:
2.148 + return None
2.149 +
2.150 +def getLocationPosition(location, locations):
2.151 +
2.152 + """
2.153 + Attempt to return the position of the given 'location' using the 'locations'
2.154 + dictionary provided. If no position can be found, return a latitude of None
2.155 + and a longitude of None.
2.156 + """
2.157 +
2.158 + latitude, longitude = None, None
2.159 +
2.160 + if location is not None:
2.161 + try:
2.162 + latitude, longitude = map(getMapReference, locations[location].split())
2.163 + except (KeyError, ValueError):
2.164 + pass
2.165 +
2.166 + return latitude, longitude
2.167 +
2.168 +# vim: tabstop=4 expandtab shiftwidth=4
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/MoinDateSupport.py Sun Jan 22 00:04:16 2012 +0100
3.3 @@ -0,0 +1,163 @@
3.4 +# -*- coding: iso-8859-1 -*-
3.5 +"""
3.6 + MoinMoin - MoinDateSupport library (derived from EventAggregatorSupport)
3.7 +
3.8 + @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk>
3.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
3.10 +"""
3.11 +
3.12 +from MoinSupport import *
3.13 +
3.14 +__version__ = "0.1"
3.15 +
3.16 +# User interface functions.
3.17 +
3.18 +def getParameterDate(arg):
3.19 +
3.20 + "Interpret 'arg', recognising keywords and simple arithmetic operations."
3.21 +
3.22 + n = None
3.23 +
3.24 + if arg is None:
3.25 + return None
3.26 +
3.27 + elif arg.startswith("current"):
3.28 + date = getCurrentDate()
3.29 + if len(arg) > 8:
3.30 + n = int(arg[7:])
3.31 +
3.32 + elif arg.startswith("yearstart"):
3.33 + date = Date((getCurrentYear(), 1, 1))
3.34 + if len(arg) > 10:
3.35 + n = int(arg[9:])
3.36 +
3.37 + elif arg.startswith("yearend"):
3.38 + date = Date((getCurrentYear(), 12, 31))
3.39 + if len(arg) > 8:
3.40 + n = int(arg[7:])
3.41 +
3.42 + else:
3.43 + date = getDate(arg)
3.44 +
3.45 + if n is not None:
3.46 + date = date.day_update(n)
3.47 +
3.48 + return date
3.49 +
3.50 +def getParameterMonth(arg):
3.51 +
3.52 + "Interpret 'arg', recognising keywords and simple arithmetic operations."
3.53 +
3.54 + n = None
3.55 +
3.56 + if arg is None:
3.57 + return None
3.58 +
3.59 + elif arg.startswith("current"):
3.60 + date = getCurrentMonth()
3.61 + if len(arg) > 8:
3.62 + n = int(arg[7:])
3.63 +
3.64 + elif arg.startswith("yearstart"):
3.65 + date = Month((getCurrentYear(), 1))
3.66 + if len(arg) > 10:
3.67 + n = int(arg[9:])
3.68 +
3.69 + elif arg.startswith("yearend"):
3.70 + date = Month((getCurrentYear(), 12))
3.71 + if len(arg) > 8:
3.72 + n = int(arg[7:])
3.73 +
3.74 + else:
3.75 + date = getMonth(arg)
3.76 +
3.77 + if n is not None:
3.78 + date = date.month_update(n)
3.79 +
3.80 + return date
3.81 +
3.82 +def getFormDate(request, calendar_name, argname):
3.83 +
3.84 + """
3.85 + Return the date from the 'request' for the calendar with the given
3.86 + 'calendar_name' using the parameter having the given 'argname'.
3.87 + """
3.88 +
3.89 + arg = getQualifiedParameter(request, calendar_name, argname)
3.90 + return getParameterDate(arg)
3.91 +
3.92 +def getFormMonth(request, calendar_name, argname):
3.93 +
3.94 + """
3.95 + Return the month from the 'request' for the calendar with the given
3.96 + 'calendar_name' using the parameter having the given 'argname'.
3.97 + """
3.98 +
3.99 + arg = getQualifiedParameter(request, calendar_name, argname)
3.100 + return getParameterMonth(arg)
3.101 +
3.102 +def getFormDateTriple(request, yeararg, montharg, dayarg):
3.103 +
3.104 + """
3.105 + Return the date from the 'request' for the calendar with the given
3.106 + 'calendar_name' using the parameters having the given 'yeararg', 'montharg'
3.107 + and 'dayarg' names.
3.108 + """
3.109 +
3.110 + year = getParameter(request, yeararg)
3.111 + month = getParameter(request, montharg)
3.112 + day = getParameter(request, dayarg)
3.113 + if year and month and day:
3.114 + return Date((int(year), int(month), int(day)))
3.115 + else:
3.116 + return None
3.117 +
3.118 +def getFormMonthPair(request, yeararg, montharg):
3.119 +
3.120 + """
3.121 + Return the month from the 'request' for the calendar with the given
3.122 + 'calendar_name' using the parameters having the given 'yeararg' and
3.123 + 'montharg' names.
3.124 + """
3.125 +
3.126 + year = getParameter(request, yeararg)
3.127 + month = getParameter(request, montharg)
3.128 + if year and month:
3.129 + return Month((int(year), int(month)))
3.130 + else:
3.131 + return None
3.132 +
3.133 +def getFullDateLabel(request, date):
3.134 +
3.135 + """
3.136 + Return the full month plus year label using the given 'request' and
3.137 + 'year_month'.
3.138 + """
3.139 +
3.140 + if not date:
3.141 + return ""
3.142 +
3.143 + _ = request.getText
3.144 + year, month, day = date.as_tuple()[:3]
3.145 + start_weekday, number_of_days = date.month_properties()
3.146 + weekday = (start_weekday + day - 1) % 7
3.147 + day_label = _(getDayLabel(weekday))
3.148 + month_label = _(getMonthLabel(month))
3.149 + return "%s %s %s %s" % (day_label, day, month_label, year)
3.150 +
3.151 +def getFullMonthLabel(request, year_month):
3.152 +
3.153 + """
3.154 + Return the full month plus year label using the given 'request' and
3.155 + 'year_month'.
3.156 + """
3.157 +
3.158 + if not year_month:
3.159 + return ""
3.160 +
3.161 + _ = request.getText
3.162 + year, month = year_month.as_tuple()[:2]
3.163 + month_label = _(getMonthLabel(month))
3.164 + return "%s %s" % (month_label, year)
3.165 +
3.166 +# vim: tabstop=4 expandtab shiftwidth=4
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/MoinSupport.py Sun Jan 22 00:04:16 2012 +0100
4.3 @@ -0,0 +1,260 @@
4.4 +# -*- coding: iso-8859-1 -*-
4.5 +"""
4.6 + MoinMoin - MoinSupport library (derived from EventAggregatorSupport)
4.7 +
4.8 + @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk>
4.9 + @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
4.10 + 2005-2008 MoinMoin:ThomasWaldmann.
4.11 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
4.12 +"""
4.13 +
4.14 +from DateSupport import *
4.15 +from MoinMoin import wikiutil
4.16 +import re
4.17 +import time
4.18 +
4.19 +__version__ = "0.1"
4.20 +
4.21 +# Content type parsing.
4.22 +
4.23 +encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?'
4.24 +encoding_regexp = re.compile(encoding_regexp_str)
4.25 +
4.26 +# Utility functions.
4.27 +
4.28 +def getContentTypeAndEncoding(content_type):
4.29 + m = encoding_regexp.search(content_type)
4.30 + if m:
4.31 + return m.group("content_type"), m.group("encoding")
4.32 + else:
4.33 + return None, None
4.34 +
4.35 +def int_or_none(x):
4.36 + if x is None:
4.37 + return x
4.38 + else:
4.39 + return int(x)
4.40 +
4.41 +# Utility classes and associated functions.
4.42 +
4.43 +class Form:
4.44 +
4.45 + """
4.46 + A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x
4.47 + environment.
4.48 + """
4.49 +
4.50 + def __init__(self, form):
4.51 + self.form = form
4.52 +
4.53 + def has_key(self, name):
4.54 + return not not self.form.getlist(name)
4.55 +
4.56 + def get(self, name, default=None):
4.57 + values = self.form.getlist(name)
4.58 + if not values:
4.59 + return default
4.60 + else:
4.61 + return values
4.62 +
4.63 + def __getitem__(self, name):
4.64 + return self.form.getlist(name)
4.65 +
4.66 +class ActionSupport:
4.67 +
4.68 + """
4.69 + Work around disruptive MoinMoin changes in 1.9, and also provide useful
4.70 + convenience methods.
4.71 + """
4.72 +
4.73 + def get_form(self):
4.74 + return get_form(self.request)
4.75 +
4.76 + def _get_selected(self, value, input_value):
4.77 +
4.78 + """
4.79 + Return the HTML attribute text indicating selection of an option (or
4.80 + otherwise) if 'value' matches 'input_value'.
4.81 + """
4.82 +
4.83 + return input_value is not None and value == input_value and 'selected="selected"' or ''
4.84 +
4.85 + def _get_selected_for_list(self, value, input_values):
4.86 +
4.87 + """
4.88 + Return the HTML attribute text indicating selection of an option (or
4.89 + otherwise) if 'value' matches one of the 'input_values'.
4.90 + """
4.91 +
4.92 + return value in input_values and 'selected="selected"' or ''
4.93 +
4.94 + def _get_input(self, form, name, default=None):
4.95 +
4.96 + """
4.97 + Return the input from 'form' having the given 'name', returning either
4.98 + the input converted to an integer or the given 'default' (optional, None
4.99 + if not specified).
4.100 + """
4.101 +
4.102 + value = form.get(name, [None])[0]
4.103 + if not value: # true if 0 obtained
4.104 + return default
4.105 + else:
4.106 + return int(value)
4.107 +
4.108 +def get_form(request):
4.109 +
4.110 + "Work around disruptive MoinMoin changes in 1.9."
4.111 +
4.112 + if hasattr(request, "values"):
4.113 + return Form(request.values)
4.114 + else:
4.115 + return request.form
4.116 +
4.117 +class send_headers_cls:
4.118 +
4.119 + """
4.120 + A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a
4.121 + 1.9.x environment.
4.122 + """
4.123 +
4.124 + def __init__(self, request):
4.125 + self.request = request
4.126 +
4.127 + def __call__(self, headers):
4.128 + for header in headers:
4.129 + parts = header.split(":")
4.130 + self.request.headers.add(parts[0], ":".join(parts[1:]))
4.131 +
4.132 +def get_send_headers(request):
4.133 +
4.134 + "Return a function that can send response headers."
4.135 +
4.136 + if hasattr(request, "http_headers"):
4.137 + return request.http_headers
4.138 + elif hasattr(request, "emit_http_headers"):
4.139 + return request.emit_http_headers
4.140 + else:
4.141 + return send_headers_cls(request)
4.142 +
4.143 +def escattr(s):
4.144 + return wikiutil.escape(s, 1)
4.145 +
4.146 +def getPathInfo(request):
4.147 + if hasattr(request, "getPathinfo"):
4.148 + return request.getPathinfo()
4.149 + else:
4.150 + return request.path
4.151 +
4.152 +# Action support functions.
4.153 +
4.154 +def getPageRevision(page):
4.155 +
4.156 + "Return the revision details dictionary for the given 'page'."
4.157 +
4.158 + # From Page.edit_info...
4.159 +
4.160 + if hasattr(page, "editlog_entry"):
4.161 + line = page.editlog_entry()
4.162 + else:
4.163 + line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x
4.164 +
4.165 + # Similar to Page.mtime_usecs behaviour...
4.166 +
4.167 + if line:
4.168 + timestamp = line.ed_time_usecs
4.169 + mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x
4.170 + comment = line.comment
4.171 + else:
4.172 + mtime = 0
4.173 + comment = ""
4.174 +
4.175 + # Leave the time zone empty.
4.176 +
4.177 + return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + (None,)), "comment" : comment}
4.178 +
4.179 +# User interface functions.
4.180 +
4.181 +def getParameter(request, name, default=None):
4.182 +
4.183 + """
4.184 + Using the given 'request', return the value of the parameter with the given
4.185 + 'name', returning the optional 'default' (or None) if no value was supplied
4.186 + in the 'request'.
4.187 + """
4.188 +
4.189 + return get_form(request).get(name, [default])[0]
4.190 +
4.191 +def getQualifiedParameter(request, prefix, argname, default=None):
4.192 +
4.193 + """
4.194 + Using the given 'request', 'prefix' and 'argname', retrieve the value of the
4.195 + qualified parameter, returning the optional 'default' (or None) if no value
4.196 + was supplied in the 'request'.
4.197 + """
4.198 +
4.199 + argname = getQualifiedParameterName(prefix, argname)
4.200 + return getParameter(request, argname, default)
4.201 +
4.202 +def getQualifiedParameterName(prefix, argname):
4.203 +
4.204 + """
4.205 + Return the qualified parameter name using the given 'prefix' and 'argname'.
4.206 + """
4.207 +
4.208 + if prefix is None:
4.209 + return argname
4.210 + else:
4.211 + return "%s-%s" % (prefix, argname)
4.212 +
4.213 +# Page-related functions.
4.214 +
4.215 +def getPrettyPageName(page):
4.216 +
4.217 + "Return a nicely formatted title/name for the given 'page'."
4.218 +
4.219 + title = page.split_title(force=1)
4.220 + return getPrettyTitle(title)
4.221 +
4.222 +def linkToPage(request, page, text, query_string=None):
4.223 +
4.224 + """
4.225 + Using 'request', return a link to 'page' with the given link 'text' and
4.226 + optional 'query_string'.
4.227 + """
4.228 +
4.229 + text = wikiutil.escape(text)
4.230 + return page.link_to_raw(request, text, query_string)
4.231 +
4.232 +def linkToResource(url, request, text, query_string=None):
4.233 +
4.234 + """
4.235 + Using 'request', return a link to 'url' with the given link 'text' and
4.236 + optional 'query_string'.
4.237 + """
4.238 +
4.239 + if query_string:
4.240 + query_string = wikiutil.makeQueryString(query_string)
4.241 + url = "%s?%s" % (url, query_string)
4.242 +
4.243 + formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter
4.244 +
4.245 + output = []
4.246 + output.append(formatter.url(1, url))
4.247 + output.append(formatter.text(text))
4.248 + output.append(formatter.url(0))
4.249 + return "".join(output)
4.250 +
4.251 +def getFullPageName(parent, title):
4.252 +
4.253 + """
4.254 + Return a full page name from the given 'parent' page (can be empty or None)
4.255 + and 'title' (a simple page name).
4.256 + """
4.257 +
4.258 + if parent:
4.259 + return "%s/%s" % (parent.rstrip("/"), title)
4.260 + else:
4.261 + return title
4.262 +
4.263 +# vim: tabstop=4 expandtab shiftwidth=4
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/PKG-INFO Sun Jan 22 00:04:16 2012 +0100
5.3 @@ -0,0 +1,21 @@
5.4 +Metadata-Version: 1.1
5.5 +Name: MoinSupport
5.6 +Version: 0.1
5.7 +Author: Paul Boddie
5.8 +Author-email: paul at boddie org uk
5.9 +Maintainer: Paul Boddie
5.10 +Maintainer-email: paul at boddie org uk
5.11 +Home-page: http://hgweb.boddie.org.uk/MoinSupport
5.12 +Download-url: http://hgweb.boddie.org.uk/MoinSupport/archive/rel-0-1.tar.bz2
5.13 +Summary: Support libraries for MoinMoin extensions
5.14 +License: GPL (version 2 or later)
5.15 +Description: The MoinSupport distribution provides libraries handling datetime
5.16 + and location-related information, along with support for Moin 1.8/1.9
5.17 + compatibility and various user interface functions.
5.18 +Keywords: MoinMoin Wiki datetime date time location
5.19 +Requires: MoinMoin
5.20 +Classifier: Development Status :: 3 - Alpha
5.21 +Classifier: License :: OSI Approved :: GNU General Public License (GPL)
5.22 +Classifier: Programming Language :: Python
5.23 +Classifier: Topic :: Internet :: WWW/HTTP
5.24 +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/README.txt Sun Jan 22 00:04:16 2012 +0100
6.3 @@ -0,0 +1,74 @@
6.4 +Introduction
6.5 +------------
6.6 +
6.7 +The MoinSupport distribution provides support libraries for use by MoinMoin
6.8 +extensions. Some of the provided modules can be used independently of
6.9 +MoinMoin, such as the DateSupport and LocationSupport modules which do not
6.10 +themselves import any MoinMoin functionality.
6.11 +
6.12 +Installation
6.13 +------------
6.14 +
6.15 +To install the support library, consider using the moinsetup tool. See the
6.16 +"Recommended Software" section below for more information.
6.17 +
6.18 +With moinsetup and a suitable configuration file, the installation is done as
6.19 +follows with $MSDIR referring to the MoinSupport distribution directory
6.20 +containing this README.txt file:
6.21 +
6.22 + python moinsetup.py -f moinsetup.cfg -m install_extension_package $MSDIR
6.23 +
6.24 +The command above uses the setup.py script provided as follows:
6.25 +
6.26 + python setup.py install --prefix=path-to-moin-prefix
6.27 +
6.28 +Recommended Software
6.29 +--------------------
6.30 +
6.31 +See the "Dependencies" section below for essential software.
6.32 +
6.33 +The moinsetup tool is recommended for installation since it aims to support
6.34 +all versions of MoinMoin that are supported for use with this software.
6.35 +
6.36 +See the following page for information on moinsetup:
6.37 +
6.38 +http://moinmo.in/ScriptMarket/moinsetup
6.39 +
6.40 +Contact, Copyright and Licence Information
6.41 +------------------------------------------
6.42 +
6.43 +See the following Web pages for more information about this work:
6.44 +
6.45 +http://hgweb.boddie.org.uk/MoinSupport
6.46 +
6.47 +The author can be contacted at the following e-mail address:
6.48 +
6.49 +paul@boddie.org.uk
6.50 +
6.51 +Copyright and licence information can be found in the docs directory - see
6.52 +docs/COPYING.txt and docs/LICENCE.txt for more information.
6.53 +
6.54 +Dependencies
6.55 +------------
6.56 +
6.57 +MoinSupport has the following basic dependencies:
6.58 +
6.59 +Packages Release Information
6.60 +-------- -------------------
6.61 +
6.62 +pytz Tested with 2007k (specifically 2007k-0ubuntu2)
6.63 + Source: http://pytz.sourceforge.net/
6.64 +
6.65 +If time zone handling is not required, pytz need not be installed. It is,
6.66 +however, highly recommended that pytz be installed.
6.67 +
6.68 +Release Procedures
6.69 +------------------
6.70 +
6.71 +Update the __version__ attributes in the modules and the setup.py version
6.72 +details.
6.73 +Change the version number and package filename/directory in the documentation.
6.74 +Update the setup.py and PKG-INFO files.
6.75 +Update the release notes (see above).
6.76 +Tag, export.
6.77 +Archive, upload.
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/docs/COPYING.txt Sun Jan 22 00:04:16 2012 +0100
7.3 @@ -0,0 +1,26 @@
7.4 +Licence Agreement
7.5 +-----------------
7.6 +
7.7 +Copyright (C) 2008, 2009, 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk>
7.8 +
7.9 +Some pieces of MoinMoin code were used in this work - typically
7.10 +pieces which demonstrate how to perform certain common tasks -
7.11 +and are thus covered by the following copyrights:
7.12 +
7.13 +Copyright (C) 2000-2004 Juergen Hermann <jh@web.de>
7.14 +Copyright (C) 2005-2008 MoinMoin:ThomasWaldmann
7.15 +
7.16 +This software is free software; you can redistribute it and/or
7.17 +modify it under the terms of the GNU General Public License as
7.18 +published by the Free Software Foundation; either version 2 of
7.19 +the License, or (at your option) any later version.
7.20 +
7.21 +This software is distributed in the hope that it will be useful,
7.22 +but WITHOUT ANY WARRANTY; without even the implied warranty of
7.23 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7.24 +GNU General Public License for more details.
7.25 +
7.26 +You should have received a copy of the GNU General Public
7.27 +License along with this library; see the file LICENCE.txt
7.28 +If not, write to the Free Software Foundation, Inc.,
7.29 +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/docs/LICENCE.txt Sun Jan 22 00:04:16 2012 +0100
8.3 @@ -0,0 +1,339 @@
8.4 + GNU GENERAL PUBLIC LICENSE
8.5 + Version 2, June 1991
8.6 +
8.7 + Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
8.8 + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
8.9 + Everyone is permitted to copy and distribute verbatim copies
8.10 + of this license document, but changing it is not allowed.
8.11 +
8.12 + Preamble
8.13 +
8.14 + The licenses for most software are designed to take away your
8.15 +freedom to share and change it. By contrast, the GNU General Public
8.16 +License is intended to guarantee your freedom to share and change free
8.17 +software--to make sure the software is free for all its users. This
8.18 +General Public License applies to most of the Free Software
8.19 +Foundation's software and to any other program whose authors commit to
8.20 +using it. (Some other Free Software Foundation software is covered by
8.21 +the GNU Lesser General Public License instead.) You can apply it to
8.22 +your programs, too.
8.23 +
8.24 + When we speak of free software, we are referring to freedom, not
8.25 +price. Our General Public Licenses are designed to make sure that you
8.26 +have the freedom to distribute copies of free software (and charge for
8.27 +this service if you wish), that you receive source code or can get it
8.28 +if you want it, that you can change the software or use pieces of it
8.29 +in new free programs; and that you know you can do these things.
8.30 +
8.31 + To protect your rights, we need to make restrictions that forbid
8.32 +anyone to deny you these rights or to ask you to surrender the rights.
8.33 +These restrictions translate to certain responsibilities for you if you
8.34 +distribute copies of the software, or if you modify it.
8.35 +
8.36 + For example, if you distribute copies of such a program, whether
8.37 +gratis or for a fee, you must give the recipients all the rights that
8.38 +you have. You must make sure that they, too, receive or can get the
8.39 +source code. And you must show them these terms so they know their
8.40 +rights.
8.41 +
8.42 + We protect your rights with two steps: (1) copyright the software, and
8.43 +(2) offer you this license which gives you legal permission to copy,
8.44 +distribute and/or modify the software.
8.45 +
8.46 + Also, for each author's protection and ours, we want to make certain
8.47 +that everyone understands that there is no warranty for this free
8.48 +software. If the software is modified by someone else and passed on, we
8.49 +want its recipients to know that what they have is not the original, so
8.50 +that any problems introduced by others will not reflect on the original
8.51 +authors' reputations.
8.52 +
8.53 + Finally, any free program is threatened constantly by software
8.54 +patents. We wish to avoid the danger that redistributors of a free
8.55 +program will individually obtain patent licenses, in effect making the
8.56 +program proprietary. To prevent this, we have made it clear that any
8.57 +patent must be licensed for everyone's free use or not licensed at all.
8.58 +
8.59 + The precise terms and conditions for copying, distribution and
8.60 +modification follow.
8.61 +
8.62 + GNU GENERAL PUBLIC LICENSE
8.63 + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
8.64 +
8.65 + 0. This License applies to any program or other work which contains
8.66 +a notice placed by the copyright holder saying it may be distributed
8.67 +under the terms of this General Public License. The "Program", below,
8.68 +refers to any such program or work, and a "work based on the Program"
8.69 +means either the Program or any derivative work under copyright law:
8.70 +that is to say, a work containing the Program or a portion of it,
8.71 +either verbatim or with modifications and/or translated into another
8.72 +language. (Hereinafter, translation is included without limitation in
8.73 +the term "modification".) Each licensee is addressed as "you".
8.74 +
8.75 +Activities other than copying, distribution and modification are not
8.76 +covered by this License; they are outside its scope. The act of
8.77 +running the Program is not restricted, and the output from the Program
8.78 +is covered only if its contents constitute a work based on the
8.79 +Program (independent of having been made by running the Program).
8.80 +Whether that is true depends on what the Program does.
8.81 +
8.82 + 1. You may copy and distribute verbatim copies of the Program's
8.83 +source code as you receive it, in any medium, provided that you
8.84 +conspicuously and appropriately publish on each copy an appropriate
8.85 +copyright notice and disclaimer of warranty; keep intact all the
8.86 +notices that refer to this License and to the absence of any warranty;
8.87 +and give any other recipients of the Program a copy of this License
8.88 +along with the Program.
8.89 +
8.90 +You may charge a fee for the physical act of transferring a copy, and
8.91 +you may at your option offer warranty protection in exchange for a fee.
8.92 +
8.93 + 2. You may modify your copy or copies of the Program or any portion
8.94 +of it, thus forming a work based on the Program, and copy and
8.95 +distribute such modifications or work under the terms of Section 1
8.96 +above, provided that you also meet all of these conditions:
8.97 +
8.98 + a) You must cause the modified files to carry prominent notices
8.99 + stating that you changed the files and the date of any change.
8.100 +
8.101 + b) You must cause any work that you distribute or publish, that in
8.102 + whole or in part contains or is derived from the Program or any
8.103 + part thereof, to be licensed as a whole at no charge to all third
8.104 + parties under the terms of this License.
8.105 +
8.106 + c) If the modified program normally reads commands interactively
8.107 + when run, you must cause it, when started running for such
8.108 + interactive use in the most ordinary way, to print or display an
8.109 + announcement including an appropriate copyright notice and a
8.110 + notice that there is no warranty (or else, saying that you provide
8.111 + a warranty) and that users may redistribute the program under
8.112 + these conditions, and telling the user how to view a copy of this
8.113 + License. (Exception: if the Program itself is interactive but
8.114 + does not normally print such an announcement, your work based on
8.115 + the Program is not required to print an announcement.)
8.116 +
8.117 +These requirements apply to the modified work as a whole. If
8.118 +identifiable sections of that work are not derived from the Program,
8.119 +and can be reasonably considered independent and separate works in
8.120 +themselves, then this License, and its terms, do not apply to those
8.121 +sections when you distribute them as separate works. But when you
8.122 +distribute the same sections as part of a whole which is a work based
8.123 +on the Program, the distribution of the whole must be on the terms of
8.124 +this License, whose permissions for other licensees extend to the
8.125 +entire whole, and thus to each and every part regardless of who wrote it.
8.126 +
8.127 +Thus, it is not the intent of this section to claim rights or contest
8.128 +your rights to work written entirely by you; rather, the intent is to
8.129 +exercise the right to control the distribution of derivative or
8.130 +collective works based on the Program.
8.131 +
8.132 +In addition, mere aggregation of another work not based on the Program
8.133 +with the Program (or with a work based on the Program) on a volume of
8.134 +a storage or distribution medium does not bring the other work under
8.135 +the scope of this License.
8.136 +
8.137 + 3. You may copy and distribute the Program (or a work based on it,
8.138 +under Section 2) in object code or executable form under the terms of
8.139 +Sections 1 and 2 above provided that you also do one of the following:
8.140 +
8.141 + a) Accompany it with the complete corresponding machine-readable
8.142 + source code, which must be distributed under the terms of Sections
8.143 + 1 and 2 above on a medium customarily used for software interchange; or,
8.144 +
8.145 + b) Accompany it with a written offer, valid for at least three
8.146 + years, to give any third party, for a charge no more than your
8.147 + cost of physically performing source distribution, a complete
8.148 + machine-readable copy of the corresponding source code, to be
8.149 + distributed under the terms of Sections 1 and 2 above on a medium
8.150 + customarily used for software interchange; or,
8.151 +
8.152 + c) Accompany it with the information you received as to the offer
8.153 + to distribute corresponding source code. (This alternative is
8.154 + allowed only for noncommercial distribution and only if you
8.155 + received the program in object code or executable form with such
8.156 + an offer, in accord with Subsection b above.)
8.157 +
8.158 +The source code for a work means the preferred form of the work for
8.159 +making modifications to it. For an executable work, complete source
8.160 +code means all the source code for all modules it contains, plus any
8.161 +associated interface definition files, plus the scripts used to
8.162 +control compilation and installation of the executable. However, as a
8.163 +special exception, the source code distributed need not include
8.164 +anything that is normally distributed (in either source or binary
8.165 +form) with the major components (compiler, kernel, and so on) of the
8.166 +operating system on which the executable runs, unless that component
8.167 +itself accompanies the executable.
8.168 +
8.169 +If distribution of executable or object code is made by offering
8.170 +access to copy from a designated place, then offering equivalent
8.171 +access to copy the source code from the same place counts as
8.172 +distribution of the source code, even though third parties are not
8.173 +compelled to copy the source along with the object code.
8.174 +
8.175 + 4. You may not copy, modify, sublicense, or distribute the Program
8.176 +except as expressly provided under this License. Any attempt
8.177 +otherwise to copy, modify, sublicense or distribute the Program is
8.178 +void, and will automatically terminate your rights under this License.
8.179 +However, parties who have received copies, or rights, from you under
8.180 +this License will not have their licenses terminated so long as such
8.181 +parties remain in full compliance.
8.182 +
8.183 + 5. You are not required to accept this License, since you have not
8.184 +signed it. However, nothing else grants you permission to modify or
8.185 +distribute the Program or its derivative works. These actions are
8.186 +prohibited by law if you do not accept this License. Therefore, by
8.187 +modifying or distributing the Program (or any work based on the
8.188 +Program), you indicate your acceptance of this License to do so, and
8.189 +all its terms and conditions for copying, distributing or modifying
8.190 +the Program or works based on it.
8.191 +
8.192 + 6. Each time you redistribute the Program (or any work based on the
8.193 +Program), the recipient automatically receives a license from the
8.194 +original licensor to copy, distribute or modify the Program subject to
8.195 +these terms and conditions. You may not impose any further
8.196 +restrictions on the recipients' exercise of the rights granted herein.
8.197 +You are not responsible for enforcing compliance by third parties to
8.198 +this License.
8.199 +
8.200 + 7. If, as a consequence of a court judgment or allegation of patent
8.201 +infringement or for any other reason (not limited to patent issues),
8.202 +conditions are imposed on you (whether by court order, agreement or
8.203 +otherwise) that contradict the conditions of this License, they do not
8.204 +excuse you from the conditions of this License. If you cannot
8.205 +distribute so as to satisfy simultaneously your obligations under this
8.206 +License and any other pertinent obligations, then as a consequence you
8.207 +may not distribute the Program at all. For example, if a patent
8.208 +license would not permit royalty-free redistribution of the Program by
8.209 +all those who receive copies directly or indirectly through you, then
8.210 +the only way you could satisfy both it and this License would be to
8.211 +refrain entirely from distribution of the Program.
8.212 +
8.213 +If any portion of this section is held invalid or unenforceable under
8.214 +any particular circumstance, the balance of the section is intended to
8.215 +apply and the section as a whole is intended to apply in other
8.216 +circumstances.
8.217 +
8.218 +It is not the purpose of this section to induce you to infringe any
8.219 +patents or other property right claims or to contest validity of any
8.220 +such claims; this section has the sole purpose of protecting the
8.221 +integrity of the free software distribution system, which is
8.222 +implemented by public license practices. Many people have made
8.223 +generous contributions to the wide range of software distributed
8.224 +through that system in reliance on consistent application of that
8.225 +system; it is up to the author/donor to decide if he or she is willing
8.226 +to distribute software through any other system and a licensee cannot
8.227 +impose that choice.
8.228 +
8.229 +This section is intended to make thoroughly clear what is believed to
8.230 +be a consequence of the rest of this License.
8.231 +
8.232 + 8. If the distribution and/or use of the Program is restricted in
8.233 +certain countries either by patents or by copyrighted interfaces, the
8.234 +original copyright holder who places the Program under this License
8.235 +may add an explicit geographical distribution limitation excluding
8.236 +those countries, so that distribution is permitted only in or among
8.237 +countries not thus excluded. In such case, this License incorporates
8.238 +the limitation as if written in the body of this License.
8.239 +
8.240 + 9. The Free Software Foundation may publish revised and/or new versions
8.241 +of the General Public License from time to time. Such new versions will
8.242 +be similar in spirit to the present version, but may differ in detail to
8.243 +address new problems or concerns.
8.244 +
8.245 +Each version is given a distinguishing version number. If the Program
8.246 +specifies a version number of this License which applies to it and "any
8.247 +later version", you have the option of following the terms and conditions
8.248 +either of that version or of any later version published by the Free
8.249 +Software Foundation. If the Program does not specify a version number of
8.250 +this License, you may choose any version ever published by the Free Software
8.251 +Foundation.
8.252 +
8.253 + 10. If you wish to incorporate parts of the Program into other free
8.254 +programs whose distribution conditions are different, write to the author
8.255 +to ask for permission. For software which is copyrighted by the Free
8.256 +Software Foundation, write to the Free Software Foundation; we sometimes
8.257 +make exceptions for this. Our decision will be guided by the two goals
8.258 +of preserving the free status of all derivatives of our free software and
8.259 +of promoting the sharing and reuse of software generally.
8.260 +
8.261 + NO WARRANTY
8.262 +
8.263 + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
8.264 +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
8.265 +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
8.266 +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
8.267 +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
8.268 +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
8.269 +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
8.270 +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
8.271 +REPAIR OR CORRECTION.
8.272 +
8.273 + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
8.274 +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
8.275 +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
8.276 +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
8.277 +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
8.278 +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
8.279 +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
8.280 +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
8.281 +POSSIBILITY OF SUCH DAMAGES.
8.282 +
8.283 + END OF TERMS AND CONDITIONS
8.284 +
8.285 + How to Apply These Terms to Your New Programs
8.286 +
8.287 + If you develop a new program, and you want it to be of the greatest
8.288 +possible use to the public, the best way to achieve this is to make it
8.289 +free software which everyone can redistribute and change under these terms.
8.290 +
8.291 + To do so, attach the following notices to the program. It is safest
8.292 +to attach them to the start of each source file to most effectively
8.293 +convey the exclusion of warranty; and each file should have at least
8.294 +the "copyright" line and a pointer to where the full notice is found.
8.295 +
8.296 + <one line to give the program's name and a brief idea of what it does.>
8.297 + Copyright (C) <year> <name of author>
8.298 +
8.299 + This program is free software; you can redistribute it and/or modify
8.300 + it under the terms of the GNU General Public License as published by
8.301 + the Free Software Foundation; either version 2 of the License, or
8.302 + (at your option) any later version.
8.303 +
8.304 + This program is distributed in the hope that it will be useful,
8.305 + but WITHOUT ANY WARRANTY; without even the implied warranty of
8.306 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8.307 + GNU General Public License for more details.
8.308 +
8.309 + You should have received a copy of the GNU General Public License along
8.310 + with this program; if not, write to the Free Software Foundation, Inc.,
8.311 + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
8.312 +
8.313 +Also add information on how to contact you by electronic and paper mail.
8.314 +
8.315 +If the program is interactive, make it output a short notice like this
8.316 +when it starts in an interactive mode:
8.317 +
8.318 + Gnomovision version 69, Copyright (C) year name of author
8.319 + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
8.320 + This is free software, and you are welcome to redistribute it
8.321 + under certain conditions; type `show c' for details.
8.322 +
8.323 +The hypothetical commands `show w' and `show c' should show the appropriate
8.324 +parts of the General Public License. Of course, the commands you use may
8.325 +be called something other than `show w' and `show c'; they could even be
8.326 +mouse-clicks or menu items--whatever suits your program.
8.327 +
8.328 +You should also get your employer (if you work as a programmer) or your
8.329 +school, if any, to sign a "copyright disclaimer" for the program, if
8.330 +necessary. Here is a sample; alter the names:
8.331 +
8.332 + Yoyodyne, Inc., hereby disclaims all copyright interest in the program
8.333 + `Gnomovision' (which makes passes at compilers) written by James Hacker.
8.334 +
8.335 + <signature of Ty Coon>, 1 April 1989
8.336 + Ty Coon, President of Vice
8.337 +
8.338 +This General Public License does not permit incorporating your program into
8.339 +proprietary programs. If your program is a subroutine library, you may
8.340 +consider it more useful to permit linking proprietary applications with the
8.341 +library. If this is what you want to do, use the GNU Lesser General
8.342 +Public License instead of this License.
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/setup.py Sun Jan 22 00:04:16 2012 +0100
9.3 @@ -0,0 +1,13 @@
9.4 +#! /usr/bin/env python
9.5 +
9.6 +from distutils.core import setup
9.7 +
9.8 +setup(
9.9 + name = "MoinSupport",
9.10 + description = "Support libraries for MoinMoin extensions",
9.11 + author = "Paul Boddie",
9.12 + author_email = "paul@boddie.org.uk",
9.13 + url = "http://hgweb.boddie.org.uk/MoinSupport",
9.14 + version = "0.1",
9.15 + py_modules = ["DateSupport", "LocationSupport", "MoinDateSupport", "MoinSupport"]
9.16 + )