EventAggregator

Annotated EventAggregatorSupport.py

14:f66fdb9607d6
2009-03-25 Paul Boddie Fixed month heading link.
paul@10 1
# -*- coding: iso-8859-1 -*-
paul@10 2
"""
paul@10 3
    MoinMoin - EventAggregator library
paul@10 4
paul@10 5
    @copyright: 2008, 2009 by Paul Boddie <paul@boddie.org.uk>
paul@10 6
    @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
paul@10 7
                2005-2008 MoinMoin:ThomasWaldmann.
paul@10 8
    @license: GNU GPL (v2 or later), see COPYING.txt for details.
paul@10 9
"""
paul@10 10
paul@10 11
from MoinMoin.Page import Page
paul@10 12
from MoinMoin import search, version
paul@10 13
import calendar
paul@11 14
import datetime
paul@10 15
import re
paul@10 16
paul@10 17
__version__ = "0.1"
paul@10 18
paul@10 19
# Regular expressions where MoinMoin does not provide the required support.
paul@10 20
paul@10 21
category_regexp = None
paul@10 22
definition_list_regexp = re.compile(ur'^\s+(?P<term>.*?)::\s(?P<desc>.*?)$', re.UNICODE | re.MULTILINE)
paul@10 23
date_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})', re.UNICODE)
paul@10 24
month_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})', re.UNICODE)
paul@10 25
paul@10 26
# Utility functions.
paul@10 27
paul@10 28
def isMoin15():
paul@10 29
    return version.release.startswith("1.5.")
paul@10 30
paul@10 31
def getCategoryPattern(request):
paul@10 32
    global category_regexp
paul@10 33
paul@10 34
    try:
paul@10 35
        return request.cfg.cache.page_category_regexact
paul@10 36
    except AttributeError:
paul@10 37
paul@10 38
        # Use regular expression from MoinMoin 1.7.1 otherwise.
paul@10 39
paul@10 40
        if category_regexp is None:
paul@10 41
            category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE)
paul@10 42
        return category_regexp
paul@10 43
paul@10 44
# The main activity functions.
paul@10 45
paul@10 46
def getPages(pagename, request):
paul@10 47
paul@10 48
    "Return the links minus category links for 'pagename' using the 'request'."
paul@10 49
paul@10 50
    query = search.QueryParser().parse_query('category:%s' % pagename)
paul@10 51
    if isMoin15():
paul@10 52
        results = search.searchPages(request, query)
paul@10 53
        results.sortByPagename()
paul@10 54
    else:
paul@10 55
        results = search.searchPages(request, query, "page_name")
paul@10 56
paul@10 57
    cat_pattern = getCategoryPattern(request)
paul@10 58
    pages = []
paul@10 59
    for page in results.hits:
paul@10 60
        if not cat_pattern.match(page.page_name):
paul@10 61
            pages.append(page)
paul@10 62
    return pages
paul@10 63
paul@10 64
def getPrettyPageName(page):
paul@10 65
paul@10 66
    "Return a nicely formatted title/name for the given 'page'."
paul@10 67
paul@10 68
    return page.split_title(force=1).replace("_", " ").replace("/", u" ? ")
paul@10 69
paul@10 70
def getEventDetails(page):
paul@10 71
paul@10 72
    "Return a dictionary of event details from the given 'page'."
paul@10 73
paul@10 74
    event_details = {}
paul@10 75
paul@10 76
    if page.pi["format"] == "wiki":
paul@10 77
        for match in definition_list_regexp.finditer(page.body):
paul@10 78
paul@10 79
            # Permit case-insensitive list terms.
paul@10 80
paul@10 81
            term = match.group("term").lower()
paul@10 82
            desc = match.group("desc")
paul@10 83
paul@10 84
            # Special value type handling.
paul@10 85
paul@10 86
            if term in ("start", "end"):
paul@10 87
                desc = getDate(desc)
paul@10 88
            elif term in ("topics",):
paul@10 89
                desc = [value.strip() for value in desc.split(",")]
paul@10 90
paul@10 91
            if desc is not None:
paul@10 92
                event_details[term] = desc
paul@10 93
paul@10 94
    return event_details
paul@10 95
paul@10 96
def getDate(s):
paul@10 97
paul@10 98
    "Parse the string 's', extracting and returning a date string."
paul@10 99
paul@10 100
    m = date_regexp.search(s)
paul@10 101
    if m:
paul@10 102
        return tuple(map(int, m.groups()))
paul@10 103
    else:
paul@10 104
        return None
paul@10 105
paul@10 106
def getMonth(s):
paul@10 107
paul@10 108
    "Parse the string 's', extracting and returning a month string."
paul@10 109
paul@10 110
    m = month_regexp.search(s)
paul@10 111
    if m:
paul@10 112
        return tuple(map(int, m.groups()))
paul@10 113
    else:
paul@10 114
        return None
paul@10 115
paul@11 116
def getCurrentMonth():
paul@11 117
paul@11 118
    "Return the current month as a (year, month) tuple."
paul@11 119
paul@11 120
    today = datetime.date.today()
paul@11 121
    return (today.year, today.month)
paul@11 122
paul@11 123
def monthupdate(date, n):
paul@11 124
paul@11 125
    "Return 'date' updated by 'n' months."
paul@11 126
paul@11 127
    if n < 0:
paul@11 128
        fn = prevmonth
paul@11 129
    else:
paul@11 130
        fn = nextmonth
paul@11 131
paul@11 132
    i = 0
paul@11 133
    while i < abs(n):
paul@11 134
        date = fn(date)
paul@11 135
        i += 1
paul@11 136
        
paul@11 137
    return date
paul@11 138
paul@13 139
def daterange(first, last, step=1):
paul@11 140
paul@13 141
    """
paul@13 142
    Get the range of dates starting at 'first' and ending on 'last', using the
paul@13 143
    specified 'step'.
paul@13 144
    """
paul@11 145
paul@10 146
    results = []
paul@10 147
paul@10 148
    months_only = len(first) == 2
paul@10 149
    start_year = first[0]
paul@10 150
    end_year = last[0]
paul@10 151
paul@11 152
    for year in range(start_year, end_year + step, step):
paul@11 153
        if step == 1 and year < end_year:
paul@10 154
            end_month = 12
paul@11 155
        elif step == -1 and year > end_year:
paul@11 156
            end_month = 1
paul@10 157
        else:
paul@10 158
            end_month = last[1]
paul@10 159
paul@11 160
        if step == 1 and year > start_year:
paul@10 161
            start_month = 1
paul@11 162
        elif step == -1 and year < start_year:
paul@11 163
            start_month = 12
paul@10 164
        else:
paul@10 165
            start_month = first[1]
paul@10 166
paul@11 167
        for month in range(start_month, end_month + step, step):
paul@10 168
            if months_only:
paul@10 169
                results.append((year, month))
paul@10 170
            else:
paul@11 171
                if step == 1 and month < end_month:
paul@10 172
                    _wd, end_day = calendar.monthrange(year, month)
paul@11 173
                elif step == -1 and month > end_month:
paul@11 174
                    end_day = 1
paul@10 175
                else:
paul@10 176
                    end_day = last[2]
paul@10 177
paul@11 178
                if step == 1 and month > start_month:
paul@10 179
                    start_day = 1
paul@11 180
                elif step == -1 and month < start_month:
paul@11 181
                    _wd, start_day = calendar.monthrange(year, month)
paul@10 182
                else:
paul@10 183
                    start_day = first[2]
paul@10 184
paul@11 185
                for day in range(start_day, end_day + step, step):
paul@10 186
                    results.append((year, month, day))
paul@10 187
paul@10 188
    return results
paul@10 189
paul@10 190
def nextdate(date):
paul@11 191
paul@11 192
    "Return the date following the given 'date'."
paul@11 193
paul@10 194
    year, month, day = date
paul@10 195
    _wd, end_day = calendar.monthrange(year, month)
paul@10 196
    if day == end_day:
paul@10 197
        if month == 12:
paul@10 198
            return (year + 1, 1, 1)
paul@10 199
        else:
paul@10 200
            return (year, month + 1, 1)
paul@10 201
    else:
paul@10 202
        return (year, month, day + 1)
paul@10 203
paul@11 204
def prevdate(date):
paul@11 205
paul@11 206
    "Return the date preceding the given 'date'."
paul@11 207
paul@11 208
    year, month, day = date
paul@11 209
    if day == 1:
paul@11 210
        if month == 1:
paul@11 211
            return (year - 1, 12, 31)
paul@11 212
        else:
paul@11 213
            _wd, end_day = calendar.monthrange(year, month - 1)
paul@11 214
            return (year, month - 1, end_day)
paul@11 215
    else:
paul@11 216
        return (year, month, day - 1)
paul@11 217
paul@11 218
def nextmonth(date):
paul@11 219
paul@11 220
    "Return the (year, month) tuple following 'date'."
paul@11 221
paul@11 222
    year, month = date
paul@11 223
    if month == 12:
paul@11 224
        return (year + 1, 1)
paul@11 225
    else:
paul@11 226
        return year, month + 1
paul@11 227
paul@11 228
def prevmonth(date):
paul@11 229
paul@11 230
    "Return the (year, month) tuple preceding 'date'."
paul@11 231
paul@11 232
    year, month = date
paul@11 233
    if month == 1:
paul@11 234
        return (year - 1, 12)
paul@11 235
    else:
paul@11 236
        return year, month - 1
paul@11 237
paul@13 238
def span(start, end):
paul@13 239
paul@13 240
    "Return the difference between 'start' and 'end'."
paul@13 241
paul@13 242
    return end[0] - start[0], end[1] - start[1]
paul@13 243
paul@10 244
def getEvents(request, category_names, calendar_start=None, calendar_end=None):
paul@10 245
paul@10 246
    """
paul@10 247
    Using the 'request', generate a list of events found on pages belonging to
paul@10 248
    the specified 'category_names', using the optional 'calendar_start' and
paul@10 249
    'calendar_end' month tuples of the form (year, month) to indicate a window
paul@10 250
    of interest.
paul@10 251
paul@10 252
    Return a list of events, a dictionary mapping months to event lists (within
paul@10 253
    the window of interest), a list of all events within the window of interest,
paul@10 254
    the earliest month of an event within the window of interest, and the latest
paul@10 255
    month of an event within the window of interest.
paul@10 256
    """
paul@10 257
paul@12 258
    # Re-order the window, if appropriate.
paul@12 259
paul@12 260
    if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
paul@12 261
        calendar_start, calendar_end = calendar_end, calendar_start
paul@12 262
paul@10 263
    events = []
paul@10 264
    shown_events = {}
paul@10 265
    all_shown_events = []
paul@10 266
paul@10 267
    earliest = None
paul@10 268
    latest = None
paul@10 269
paul@10 270
    for category_name in category_names:
paul@10 271
paul@10 272
        # Get the pages and page names in the category.
paul@10 273
paul@10 274
        pages_in_category = getPages(category_name, request)
paul@10 275
paul@10 276
        # Visit each page in the category.
paul@10 277
paul@10 278
        for page_in_category in pages_in_category:
paul@10 279
            pagename = page_in_category.page_name
paul@10 280
paul@10 281
            # Get a real page, not a result page.
paul@10 282
paul@10 283
            real_page_in_category = Page(request, pagename)
paul@10 284
            event_details = getEventDetails(real_page_in_category)
paul@10 285
paul@10 286
            # Define the event as the page together with its details.
paul@10 287
paul@10 288
            event = (real_page_in_category, event_details)
paul@10 289
            events.append(event)
paul@10 290
paul@10 291
            # Test for the suitability of the event.
paul@10 292
paul@10 293
            if event_details.has_key("start") and event_details.has_key("end"):
paul@10 294
paul@10 295
                start_month = event_details["start"][:2]
paul@10 296
                end_month = event_details["end"][:2]
paul@10 297
paul@10 298
                # Compare the months of the dates to the requested calendar
paul@10 299
                # window, if any.
paul@10 300
paul@10 301
                if (calendar_start is None or end_month >= calendar_start) and \
paul@10 302
                    (calendar_end is None or start_month <= calendar_end):
paul@10 303
paul@10 304
                    all_shown_events.append(event)
paul@10 305
paul@10 306
                    if earliest is None or start_month < earliest:
paul@10 307
                        earliest = start_month
paul@10 308
                    if latest is None or end_month > latest:
paul@10 309
                        latest = end_month
paul@10 310
paul@10 311
                    # Store the event in the month-specific dictionary.
paul@10 312
paul@10 313
                    first = max(start_month, calendar_start or start_month)
paul@10 314
                    last = min(end_month, calendar_end or end_month)
paul@10 315
paul@10 316
                    for event_month in daterange(first, last):
paul@10 317
                        if not shown_events.has_key(event_month):
paul@10 318
                            shown_events[event_month] = []
paul@10 319
                        shown_events[event_month].append(event)
paul@10 320
paul@10 321
    return events, shown_events, all_shown_events, earliest, latest
paul@10 322
paul@13 323
def getConcretePeriod(calendar_start, calendar_end, earliest, latest):
paul@13 324
paul@13 325
    """
paul@13 326
    From the requested 'calendar_start' and 'calendar_end', which may be None,
paul@13 327
    indicating that no restriction is imposed on the period for each of the
paul@13 328
    boundaries, use the 'earliest' and 'latest' event months to define a
paul@13 329
    specific period of interest.
paul@13 330
    """
paul@13 331
paul@13 332
    # Define the period as starting with any specified start month or the
paul@13 333
    # earliest event known, ending with any specified end month or the latest
paul@13 334
    # event known.
paul@13 335
paul@13 336
    first = calendar_start or earliest
paul@13 337
    last = calendar_end or latest
paul@13 338
paul@13 339
    # If there is no range of months to show, perhaps because there are no
paul@13 340
    # events in the requested period, and there was no start or end month
paul@13 341
    # specified, show only the month indicated by the start or end of the
paul@13 342
    # requested period. If all events were to be shown but none were found show
paul@13 343
    # the current month.
paul@13 344
paul@13 345
    if first is None:
paul@13 346
        first = last or getCurrentMonth()
paul@13 347
    if last is None:
paul@13 348
        last = first or getCurrentMonth()
paul@13 349
paul@13 350
    # Permit "expiring" periods (where the start date approaches the end date).
paul@13 351
paul@13 352
    return min(first, last), last
paul@13 353
paul@10 354
# vim: tabstop=4 expandtab shiftwidth=4