EventAggregator

Annotated EventAggregatorSupport.py

47:1cbad5c4b7f0
2009-06-06 Paul Boddie Moved event body manipulation to the EventAggregatorSupport module, employing an approach similar to that used when retrieving event details from page bodies. Changed the definition list regular expression to support recognition of values in commented-out regions, meaning that such regions must now be recognised and discarded when retrieving event details from pages. Changed the parameter values sent by the macro in links from day numbers in calendar views. Introduced better behaviour when interpreting request parameters in the EventAggregatorNewEvent action, providing the current year as the default start year. Removed the redirect for cancelled page edits since it gives the wrong impression about whether a page has been created (which has already happened by the time the user gets to edit the page). Added documentation for the EventAggregatorNewEvent action. Updated documentation for the EventAggregator macro. Updated the release information. Added package-related files.
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@24 13
from MoinMoin import wikiutil
paul@10 14
import calendar
paul@11 15
import datetime
paul@24 16
import time
paul@10 17
import re
paul@10 18
paul@45 19
__version__ = "0.3"
paul@10 20
paul@22 21
# Date labels.
paul@22 22
paul@22 23
month_labels = ["January", "February", "March", "April", "May", "June",
paul@22 24
    "July", "August", "September", "October", "November", "December"]
paul@22 25
weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
paul@22 26
paul@10 27
# Regular expressions where MoinMoin does not provide the required support.
paul@10 28
paul@10 29
category_regexp = None
paul@47 30
paul@47 31
# Page parsing.
paul@47 32
paul@47 33
definition_list_regexp = re.compile(ur'(?P<wholeterm>^(?P<optcomment>#*)\s+(?P<term>.*?)::\s)(?P<desc>.*?)$', re.UNICODE | re.MULTILINE)
paul@47 34
category_membership_regexp = re.compile(ur"^\s*((Category\S+)(\s+Category\S+)*)\s*$", re.MULTILINE | re.UNICODE)
paul@47 35
paul@47 36
# Value parsing.
paul@47 37
paul@10 38
date_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})', re.UNICODE)
paul@10 39
month_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})', re.UNICODE)
paul@19 40
verbatim_regexp = re.compile(ur'(?:'
paul@19 41
    ur'<<Verbatim\((?P<verbatim>.*?)\)>>'
paul@19 42
    ur'|'
paul@19 43
    ur'\[\[Verbatim\((?P<verbatim2>.*?)\)\]\]'
paul@19 44
    ur'|'
paul@19 45
    ur'`(?P<monospace>.*?)`'
paul@19 46
    ur'|'
paul@19 47
    ur'{{{(?P<preformatted>.*?)}}}'
paul@19 48
    ur')', re.UNICODE)
paul@10 49
paul@10 50
# Utility functions.
paul@10 51
paul@10 52
def isMoin15():
paul@10 53
    return version.release.startswith("1.5.")
paul@10 54
paul@10 55
def getCategoryPattern(request):
paul@10 56
    global category_regexp
paul@10 57
paul@10 58
    try:
paul@10 59
        return request.cfg.cache.page_category_regexact
paul@10 60
    except AttributeError:
paul@10 61
paul@10 62
        # Use regular expression from MoinMoin 1.7.1 otherwise.
paul@10 63
paul@10 64
        if category_regexp is None:
paul@10 65
            category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE)
paul@10 66
        return category_regexp
paul@10 67
paul@19 68
# Action support functions.
paul@19 69
paul@19 70
def getCategories(request):
paul@19 71
paul@19 72
    """
paul@19 73
    From the AdvancedSearch macro, return a list of category page names using
paul@19 74
    the given 'request'.
paul@19 75
    """
paul@19 76
paul@19 77
    # This will return all pages with "Category" in the title.
paul@19 78
paul@19 79
    cat_filter = getCategoryPattern(request).search
paul@19 80
    return request.rootpage.getPageList(filter=cat_filter)
paul@19 81
paul@19 82
def getCategoryMapping(category_pagenames, request):
paul@19 83
paul@19 84
    """
paul@19 85
    For the given 'category_pagenames' return a list of tuples of the form
paul@19 86
    (category name, category page name) using the given 'request'.
paul@19 87
    """
paul@19 88
paul@19 89
    cat_pattern = getCategoryPattern(request)
paul@19 90
    mapping = []
paul@19 91
    for pagename in category_pagenames:
paul@19 92
        name = cat_pattern.match(pagename).group("key")
paul@19 93
        if name != "Category":
paul@19 94
            mapping.append((name, pagename))
paul@19 95
    mapping.sort()
paul@19 96
    return mapping
paul@19 97
paul@30 98
def getPageRevision(page):
paul@24 99
paul@30 100
    # From Page.edit_info...
paul@24 101
paul@30 102
    if hasattr(page, "editlog_entry"):
paul@30 103
        line = page.editlog_entry()
paul@27 104
    else:
paul@30 105
        line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x
paul@27 106
paul@30 107
    timestamp = line.ed_time_usecs
paul@30 108
    mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x
paul@30 109
    return {"timestamp" : time.gmtime(mtime), "comment" : line.comment}
paul@29 110
paul@29 111
def getHTTPTimeString(tmtuple):
paul@29 112
    return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
paul@29 113
        weekday_labels[tmtuple.tm_wday],
paul@29 114
        tmtuple.tm_mday,
paul@29 115
        month_labels[tmtuple.tm_mon -1], # zero-based labels
paul@29 116
        tmtuple.tm_year,
paul@29 117
        tmtuple.tm_hour,
paul@29 118
        tmtuple.tm_min,
paul@29 119
        tmtuple.tm_sec
paul@29 120
        )
paul@24 121
paul@10 122
# The main activity functions.
paul@10 123
paul@10 124
def getPages(pagename, request):
paul@10 125
paul@10 126
    "Return the links minus category links for 'pagename' using the 'request'."
paul@10 127
paul@10 128
    query = search.QueryParser().parse_query('category:%s' % pagename)
paul@10 129
    if isMoin15():
paul@10 130
        results = search.searchPages(request, query)
paul@10 131
        results.sortByPagename()
paul@10 132
    else:
paul@10 133
        results = search.searchPages(request, query, "page_name")
paul@10 134
paul@10 135
    cat_pattern = getCategoryPattern(request)
paul@10 136
    pages = []
paul@10 137
    for page in results.hits:
paul@10 138
        if not cat_pattern.match(page.page_name):
paul@10 139
            pages.append(page)
paul@10 140
    return pages
paul@10 141
paul@24 142
def getSimpleWikiText(text):
paul@24 143
paul@24 144
    """
paul@24 145
    Return the plain text representation of the given 'text' which may employ
paul@24 146
    certain Wiki syntax features, such as those providing verbatim or monospaced
paul@24 147
    text.
paul@24 148
    """
paul@24 149
paul@24 150
    # NOTE: Re-implementing support for verbatim text and linking avoidance.
paul@24 151
paul@24 152
    return "".join([s for s in verbatim_regexp.split(text) if s is not None])
paul@24 153
paul@47 154
def getEncodedWikiText(text):
paul@47 155
paul@47 156
    "Encode the given 'text' in a verbatim representation."
paul@47 157
paul@47 158
    return "<<Verbatim(%s)>>" % text
paul@47 159
paul@27 160
def getFormat(page):
paul@27 161
paul@27 162
    "Get the format used on 'page'."
paul@27 163
paul@27 164
    if isMoin15():
paul@27 165
        return "wiki" # page.pi_format
paul@27 166
    else:
paul@27 167
        return page.pi["format"]
paul@27 168
paul@10 169
def getEventDetails(page):
paul@10 170
paul@10 171
    "Return a dictionary of event details from the given 'page'."
paul@10 172
paul@10 173
    event_details = {}
paul@10 174
paul@27 175
    if getFormat(page) == "wiki":
paul@27 176
        for match in definition_list_regexp.finditer(page.get_raw_body()):
paul@10 177
paul@47 178
            # Skip commented-out items.
paul@47 179
paul@47 180
            if match.group("optcomment"):
paul@47 181
                continue
paul@47 182
paul@10 183
            # Permit case-insensitive list terms.
paul@10 184
paul@10 185
            term = match.group("term").lower()
paul@10 186
            desc = match.group("desc")
paul@10 187
paul@10 188
            # Special value type handling.
paul@10 189
paul@19 190
            # Dates.
paul@19 191
paul@10 192
            if term in ("start", "end"):
paul@10 193
                desc = getDate(desc)
paul@19 194
paul@24 195
            # Lists (whose elements may be quoted).
paul@19 196
paul@24 197
            elif term in ("topics", "categories"):
paul@24 198
                desc = [getSimpleWikiText(value.strip()) for value in desc.split(",")]
paul@10 199
paul@19 200
            # Labels which may well be quoted.
paul@19 201
paul@45 202
            elif term in ("title", "summary", "description"):
paul@24 203
                desc = getSimpleWikiText(desc)
paul@19 204
paul@10 205
            if desc is not None:
paul@10 206
                event_details[term] = desc
paul@10 207
paul@10 208
    return event_details
paul@10 209
paul@47 210
def setEventDetails(body, event_details):
paul@47 211
paul@47 212
    """
paul@47 213
    Set the event details in the given page 'body' using the 'event_details'
paul@47 214
    dictionary, returning the new body text.
paul@47 215
    """
paul@47 216
paul@47 217
    new_body_parts = []
paul@47 218
    end_of_last_match = 0
paul@47 219
paul@47 220
    for match in definition_list_regexp.finditer(body):
paul@47 221
paul@47 222
        # Add preceding text to the new body.
paul@47 223
paul@47 224
        new_body_parts.append(body[end_of_last_match:match.start()])
paul@47 225
        end_of_last_match = match.end()
paul@47 226
paul@47 227
        # Get the matching regions, adding the term to the new body.
paul@47 228
paul@47 229
        new_body_parts.append(match.group("wholeterm"))
paul@47 230
paul@47 231
        # Permit case-insensitive list terms.
paul@47 232
paul@47 233
        term = match.group("term").lower()
paul@47 234
        desc = match.group("desc")
paul@47 235
paul@47 236
        # Special value type handling.
paul@47 237
paul@47 238
        if event_details.has_key(term):
paul@47 239
paul@47 240
            # Dates.
paul@47 241
paul@47 242
            if term in ("start", "end"):
paul@47 243
                desc = desc.replace("YYYY-MM-DD", event_details[term])
paul@47 244
paul@47 245
            # Lists (whose elements may be quoted).
paul@47 246
paul@47 247
            elif term in ("topics", "categories"):
paul@47 248
                desc = ", ".join(getEncodedWikiText(event_details[term]))
paul@47 249
paul@47 250
            # Labels which may well be quoted.
paul@47 251
paul@47 252
            elif term in ("title", "summary", "description"):
paul@47 253
                desc = getEncodedWikiText(event_details[term])
paul@47 254
paul@47 255
        new_body_parts.append(desc)
paul@47 256
paul@47 257
    else:
paul@47 258
        new_body_parts.append(body[end_of_last_match:])
paul@47 259
paul@47 260
    return "".join(new_body_parts)
paul@47 261
paul@47 262
def setCategoryMembership(body, category_names):
paul@47 263
paul@47 264
    """
paul@47 265
    Set the category membership in the given page 'body' using the specified
paul@47 266
    'category_names' and returning the new body text.
paul@47 267
    """
paul@47 268
paul@47 269
    match = category_membership_regexp.search(body)
paul@47 270
    if match:
paul@47 271
        return "".join([body[:match.start()], " ".join(category_names), body[match.end():]])
paul@47 272
    else:
paul@47 273
        return body
paul@47 274
paul@19 275
def getEventSummary(event_page, event_details):
paul@19 276
paul@19 277
    """
paul@19 278
    Return either the given title or summary of the event described by the given
paul@19 279
    'event_page', according to the given 'event_details', or return the pretty
paul@19 280
    version of the page name.
paul@19 281
    """
paul@19 282
paul@19 283
    if event_details.has_key("title"):
paul@19 284
        return event_details["title"]
paul@19 285
    elif event_details.has_key("summary"):
paul@19 286
        return event_details["summary"]
paul@19 287
    else:
paul@19 288
        return getPrettyPageName(event_page)
paul@19 289
paul@10 290
def getDate(s):
paul@10 291
paul@10 292
    "Parse the string 's', extracting and returning a date string."
paul@10 293
paul@10 294
    m = date_regexp.search(s)
paul@10 295
    if m:
paul@10 296
        return tuple(map(int, m.groups()))
paul@10 297
    else:
paul@10 298
        return None
paul@10 299
paul@10 300
def getMonth(s):
paul@10 301
paul@10 302
    "Parse the string 's', extracting and returning a month string."
paul@10 303
paul@10 304
    m = month_regexp.search(s)
paul@10 305
    if m:
paul@10 306
        return tuple(map(int, m.groups()))
paul@10 307
    else:
paul@10 308
        return None
paul@10 309
paul@11 310
def getCurrentMonth():
paul@11 311
paul@11 312
    "Return the current month as a (year, month) tuple."
paul@11 313
paul@11 314
    today = datetime.date.today()
paul@11 315
    return (today.year, today.month)
paul@11 316
paul@17 317
def getCurrentYear():
paul@17 318
paul@17 319
    "Return the current year."
paul@17 320
paul@17 321
    today = datetime.date.today()
paul@17 322
    return today.year
paul@17 323
paul@11 324
def monthupdate(date, n):
paul@11 325
paul@11 326
    "Return 'date' updated by 'n' months."
paul@11 327
paul@11 328
    if n < 0:
paul@11 329
        fn = prevmonth
paul@11 330
    else:
paul@11 331
        fn = nextmonth
paul@11 332
paul@11 333
    i = 0
paul@11 334
    while i < abs(n):
paul@11 335
        date = fn(date)
paul@11 336
        i += 1
paul@11 337
        
paul@11 338
    return date
paul@11 339
paul@13 340
def daterange(first, last, step=1):
paul@11 341
paul@13 342
    """
paul@13 343
    Get the range of dates starting at 'first' and ending on 'last', using the
paul@13 344
    specified 'step'.
paul@13 345
    """
paul@11 346
paul@10 347
    results = []
paul@10 348
paul@10 349
    months_only = len(first) == 2
paul@10 350
    start_year = first[0]
paul@10 351
    end_year = last[0]
paul@10 352
paul@11 353
    for year in range(start_year, end_year + step, step):
paul@11 354
        if step == 1 and year < end_year:
paul@10 355
            end_month = 12
paul@11 356
        elif step == -1 and year > end_year:
paul@11 357
            end_month = 1
paul@10 358
        else:
paul@10 359
            end_month = last[1]
paul@10 360
paul@11 361
        if step == 1 and year > start_year:
paul@10 362
            start_month = 1
paul@11 363
        elif step == -1 and year < start_year:
paul@11 364
            start_month = 12
paul@10 365
        else:
paul@10 366
            start_month = first[1]
paul@10 367
paul@11 368
        for month in range(start_month, end_month + step, step):
paul@10 369
            if months_only:
paul@10 370
                results.append((year, month))
paul@10 371
            else:
paul@11 372
                if step == 1 and month < end_month:
paul@10 373
                    _wd, end_day = calendar.monthrange(year, month)
paul@11 374
                elif step == -1 and month > end_month:
paul@11 375
                    end_day = 1
paul@10 376
                else:
paul@10 377
                    end_day = last[2]
paul@10 378
paul@11 379
                if step == 1 and month > start_month:
paul@10 380
                    start_day = 1
paul@11 381
                elif step == -1 and month < start_month:
paul@11 382
                    _wd, start_day = calendar.monthrange(year, month)
paul@10 383
                else:
paul@10 384
                    start_day = first[2]
paul@10 385
paul@11 386
                for day in range(start_day, end_day + step, step):
paul@10 387
                    results.append((year, month, day))
paul@10 388
paul@10 389
    return results
paul@10 390
paul@10 391
def nextdate(date):
paul@11 392
paul@11 393
    "Return the date following the given 'date'."
paul@11 394
paul@10 395
    year, month, day = date
paul@10 396
    _wd, end_day = calendar.monthrange(year, month)
paul@10 397
    if day == end_day:
paul@10 398
        if month == 12:
paul@10 399
            return (year + 1, 1, 1)
paul@10 400
        else:
paul@10 401
            return (year, month + 1, 1)
paul@10 402
    else:
paul@10 403
        return (year, month, day + 1)
paul@10 404
paul@11 405
def prevdate(date):
paul@11 406
paul@11 407
    "Return the date preceding the given 'date'."
paul@11 408
paul@11 409
    year, month, day = date
paul@11 410
    if day == 1:
paul@11 411
        if month == 1:
paul@11 412
            return (year - 1, 12, 31)
paul@11 413
        else:
paul@11 414
            _wd, end_day = calendar.monthrange(year, month - 1)
paul@11 415
            return (year, month - 1, end_day)
paul@11 416
    else:
paul@11 417
        return (year, month, day - 1)
paul@11 418
paul@11 419
def nextmonth(date):
paul@11 420
paul@11 421
    "Return the (year, month) tuple following 'date'."
paul@11 422
paul@11 423
    year, month = date
paul@11 424
    if month == 12:
paul@11 425
        return (year + 1, 1)
paul@11 426
    else:
paul@11 427
        return year, month + 1
paul@11 428
paul@11 429
def prevmonth(date):
paul@11 430
paul@11 431
    "Return the (year, month) tuple preceding 'date'."
paul@11 432
paul@11 433
    year, month = date
paul@11 434
    if month == 1:
paul@11 435
        return (year - 1, 12)
paul@11 436
    else:
paul@11 437
        return year, month - 1
paul@11 438
paul@13 439
def span(start, end):
paul@13 440
paul@13 441
    "Return the difference between 'start' and 'end'."
paul@13 442
paul@13 443
    return end[0] - start[0], end[1] - start[1]
paul@13 444
paul@10 445
def getEvents(request, category_names, calendar_start=None, calendar_end=None):
paul@10 446
paul@10 447
    """
paul@10 448
    Using the 'request', generate a list of events found on pages belonging to
paul@10 449
    the specified 'category_names', using the optional 'calendar_start' and
paul@10 450
    'calendar_end' month tuples of the form (year, month) to indicate a window
paul@10 451
    of interest.
paul@10 452
paul@10 453
    Return a list of events, a dictionary mapping months to event lists (within
paul@10 454
    the window of interest), a list of all events within the window of interest,
paul@10 455
    the earliest month of an event within the window of interest, and the latest
paul@10 456
    month of an event within the window of interest.
paul@10 457
    """
paul@10 458
paul@12 459
    # Re-order the window, if appropriate.
paul@12 460
paul@12 461
    if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
paul@12 462
        calendar_start, calendar_end = calendar_end, calendar_start
paul@12 463
paul@10 464
    events = []
paul@10 465
    shown_events = {}
paul@10 466
    all_shown_events = []
paul@17 467
    processed_pages = set()
paul@10 468
paul@10 469
    earliest = None
paul@10 470
    latest = None
paul@10 471
paul@10 472
    for category_name in category_names:
paul@10 473
paul@10 474
        # Get the pages and page names in the category.
paul@10 475
paul@10 476
        pages_in_category = getPages(category_name, request)
paul@10 477
paul@10 478
        # Visit each page in the category.
paul@10 479
paul@10 480
        for page_in_category in pages_in_category:
paul@10 481
            pagename = page_in_category.page_name
paul@10 482
paul@17 483
            # Only process each page once.
paul@17 484
paul@17 485
            if pagename in processed_pages:
paul@17 486
                continue
paul@17 487
            else:
paul@17 488
                processed_pages.add(pagename)
paul@17 489
paul@10 490
            # Get a real page, not a result page.
paul@10 491
paul@10 492
            real_page_in_category = Page(request, pagename)
paul@10 493
            event_details = getEventDetails(real_page_in_category)
paul@10 494
paul@10 495
            # Define the event as the page together with its details.
paul@10 496
paul@10 497
            event = (real_page_in_category, event_details)
paul@10 498
            events.append(event)
paul@10 499
paul@10 500
            # Test for the suitability of the event.
paul@10 501
paul@10 502
            if event_details.has_key("start") and event_details.has_key("end"):
paul@10 503
paul@10 504
                start_month = event_details["start"][:2]
paul@10 505
                end_month = event_details["end"][:2]
paul@10 506
paul@10 507
                # Compare the months of the dates to the requested calendar
paul@10 508
                # window, if any.
paul@10 509
paul@10 510
                if (calendar_start is None or end_month >= calendar_start) and \
paul@10 511
                    (calendar_end is None or start_month <= calendar_end):
paul@10 512
paul@10 513
                    all_shown_events.append(event)
paul@10 514
paul@10 515
                    if earliest is None or start_month < earliest:
paul@10 516
                        earliest = start_month
paul@10 517
                    if latest is None or end_month > latest:
paul@10 518
                        latest = end_month
paul@10 519
paul@10 520
                    # Store the event in the month-specific dictionary.
paul@10 521
paul@10 522
                    first = max(start_month, calendar_start or start_month)
paul@10 523
                    last = min(end_month, calendar_end or end_month)
paul@10 524
paul@10 525
                    for event_month in daterange(first, last):
paul@10 526
                        if not shown_events.has_key(event_month):
paul@10 527
                            shown_events[event_month] = []
paul@10 528
                        shown_events[event_month].append(event)
paul@10 529
paul@10 530
    return events, shown_events, all_shown_events, earliest, latest
paul@10 531
paul@29 532
def setEventTimestamps(request, events):
paul@29 533
paul@29 534
    """
paul@29 535
    Using 'request', set timestamp details in the details dictionary of each of
paul@29 536
    the 'events': a list of the form (event_page, event_details).
paul@29 537
paul@29 538
    Retutn the latest timestamp found.
paul@29 539
    """
paul@29 540
paul@29 541
    latest = None
paul@29 542
paul@29 543
    for event_page, event_details in events:
paul@29 544
paul@29 545
        # Get the initial revision of the page.
paul@29 546
paul@29 547
        revisions = event_page.getRevList()
paul@29 548
        event_page_initial = Page(request, event_page.page_name, rev=revisions[-1])
paul@29 549
paul@29 550
        # Get the created and last modified times.
paul@29 551
paul@30 552
        initial_revision = getPageRevision(event_page_initial)
paul@30 553
        event_details["created"] = initial_revision["timestamp"]
paul@30 554
        latest_revision = getPageRevision(event_page)
paul@30 555
        event_details["last-modified"] = latest_revision["timestamp"]
paul@29 556
        event_details["sequence"] = len(revisions) - 1
paul@30 557
        event_details["last-comment"] = latest_revision["comment"]
paul@29 558
paul@29 559
        if latest is None or latest < event_details["last-modified"]:
paul@29 560
            latest = event_details["last-modified"]
paul@29 561
paul@29 562
    return latest
paul@29 563
paul@26 564
def compareEvents(event1, event2):
paul@26 565
paul@26 566
    """
paul@26 567
    Compare 'event1' and 'event2' by start and end date, where both parameters
paul@26 568
    are of the following form:
paul@26 569
paul@26 570
    (event_page, event_details)
paul@26 571
    """
paul@26 572
paul@26 573
    event_page1, event_details1 = event1
paul@26 574
    event_page2, event_details2 = event2
paul@26 575
    return cmp(
paul@26 576
        (event_details1["start"], event_details1["end"]),
paul@26 577
        (event_details2["start"], event_details2["end"])
paul@26 578
        )
paul@26 579
paul@26 580
def getOrderedEvents(events):
paul@26 581
paul@26 582
    """
paul@26 583
    Return a list with the given 'events' ordered according to their start and
paul@26 584
    end dates. Each list element must be of the following form:
paul@26 585
paul@26 586
    (event_page, event_details)
paul@26 587
    """
paul@26 588
paul@26 589
    ordered_events = events[:]
paul@26 590
    ordered_events.sort(compareEvents)
paul@26 591
    return ordered_events
paul@26 592
paul@13 593
def getConcretePeriod(calendar_start, calendar_end, earliest, latest):
paul@13 594
paul@13 595
    """
paul@13 596
    From the requested 'calendar_start' and 'calendar_end', which may be None,
paul@13 597
    indicating that no restriction is imposed on the period for each of the
paul@13 598
    boundaries, use the 'earliest' and 'latest' event months to define a
paul@13 599
    specific period of interest.
paul@13 600
    """
paul@13 601
paul@13 602
    # Define the period as starting with any specified start month or the
paul@13 603
    # earliest event known, ending with any specified end month or the latest
paul@13 604
    # event known.
paul@13 605
paul@13 606
    first = calendar_start or earliest
paul@13 607
    last = calendar_end or latest
paul@13 608
paul@13 609
    # If there is no range of months to show, perhaps because there are no
paul@13 610
    # events in the requested period, and there was no start or end month
paul@13 611
    # specified, show only the month indicated by the start or end of the
paul@13 612
    # requested period. If all events were to be shown but none were found show
paul@13 613
    # the current month.
paul@13 614
paul@13 615
    if first is None:
paul@13 616
        first = last or getCurrentMonth()
paul@13 617
    if last is None:
paul@13 618
        last = first or getCurrentMonth()
paul@13 619
paul@13 620
    # Permit "expiring" periods (where the start date approaches the end date).
paul@13 621
paul@13 622
    return min(first, last), last
paul@13 623
paul@15 624
def getCoverage(start, end, events):
paul@15 625
paul@15 626
    """
paul@15 627
    Within the period defined by the 'start' and 'end' dates, determine the
paul@15 628
    coverage of the days in the period by the given 'events', returning a set of
paul@15 629
    covered days, along with a list of slots, where each slot contains a tuple
paul@15 630
    of the form (set of covered days, events).
paul@15 631
    """
paul@15 632
paul@15 633
    all_events = []
paul@15 634
    full_coverage = set()
paul@15 635
paul@15 636
    # Get event details.
paul@15 637
paul@15 638
    for event in events:
paul@15 639
        event_page, event_details = event
paul@15 640
paul@15 641
        # Test for the event in the period.
paul@15 642
paul@15 643
        if event_details["start"] <= end and event_details["end"] >= start:
paul@15 644
paul@15 645
            # Find the coverage of this period for the event.
paul@15 646
paul@15 647
            event_start = max(event_details["start"], start)
paul@15 648
            event_end = min(event_details["end"], end)
paul@15 649
            event_coverage = set(daterange(event_start, event_end))
paul@15 650
paul@15 651
            # Update the overall coverage.
paul@15 652
paul@15 653
            full_coverage.update(event_coverage)
paul@15 654
paul@15 655
            # Try and fit the event into the events list.
paul@15 656
paul@15 657
            for i, (coverage, covered_events) in enumerate(all_events):
paul@15 658
paul@15 659
                # Where the event does not overlap with the current
paul@15 660
                # element, add it alongside existing events.
paul@15 661
paul@15 662
                if not coverage.intersection(event_coverage):
paul@15 663
                    covered_events.append(event)
paul@15 664
                    all_events[i] = coverage.union(event_coverage), covered_events
paul@15 665
                    break
paul@15 666
paul@15 667
            # Make a new element in the list if the event cannot be
paul@15 668
            # marked alongside existing events.
paul@15 669
paul@15 670
            else:
paul@15 671
                all_events.append((event_coverage, [event]))
paul@15 672
paul@15 673
    return full_coverage, all_events
paul@15 674
paul@19 675
# User interface functions.
paul@19 676
paul@23 677
def getParameter(request, name):
paul@23 678
    return request.form.get(name, [None])[0]
paul@23 679
paul@19 680
def getParameterMonth(arg):
paul@19 681
    n = None
paul@19 682
paul@19 683
    if arg.startswith("current"):
paul@19 684
        date = getCurrentMonth()
paul@19 685
        if len(arg) > 8:
paul@19 686
            n = int(arg[7:])
paul@19 687
paul@19 688
    elif arg.startswith("yearstart"):
paul@19 689
        date = (getCurrentYear(), 1)
paul@19 690
        if len(arg) > 10:
paul@19 691
            n = int(arg[9:])
paul@19 692
paul@19 693
    elif arg.startswith("yearend"):
paul@19 694
        date = (getCurrentYear(), 12)
paul@19 695
        if len(arg) > 8:
paul@19 696
            n = int(arg[7:])
paul@19 697
paul@19 698
    else:
paul@19 699
        date = getMonth(arg)
paul@19 700
paul@19 701
    if n is not None:
paul@19 702
        date = monthupdate(date, n)
paul@19 703
paul@19 704
    return date
paul@19 705
paul@19 706
def getFormMonth(request, calendar_name, argname):
paul@19 707
    if calendar_name is None:
paul@19 708
        calendar_prefix = argname
paul@19 709
    else:
paul@19 710
        calendar_prefix = "%s-%s" % (calendar_name, argname)
paul@19 711
paul@23 712
    arg = getParameter(request, calendar_prefix)
paul@19 713
    if arg is not None:
paul@19 714
        return getParameterMonth(arg)
paul@19 715
    else:
paul@19 716
        return None
paul@19 717
paul@23 718
def getFormMonthPair(request, yeararg, montharg):
paul@23 719
    year = getParameter(request, yeararg)
paul@23 720
    month = getParameter(request, montharg)
paul@23 721
    if year and month:
paul@23 722
        return (int(year), int(month))
paul@23 723
    else:
paul@23 724
        return None
paul@23 725
paul@19 726
def getPrettyPageName(page):
paul@19 727
paul@19 728
    "Return a nicely formatted title/name for the given 'page'."
paul@19 729
paul@27 730
    if isMoin15():
paul@27 731
        title = page.split_title(page.request, force=1)
paul@27 732
    else:
paul@27 733
        title = page.split_title(force=1)
paul@27 734
paul@27 735
    return title.replace("_", " ").replace("/", u" ? ")
paul@19 736
paul@22 737
def getMonthLabel(month):
paul@22 738
paul@22 739
    "Return an unlocalised label for the given 'month'."
paul@22 740
paul@22 741
    return month_labels[month - 1] # zero-based labels
paul@22 742
paul@22 743
def getDayLabel(weekday):
paul@22 744
paul@22 745
    "Return an unlocalised label for the given 'weekday'."
paul@22 746
paul@22 747
    return weekday_labels[weekday]
paul@22 748
paul@27 749
def linkToPage(request, page, text, query_string=None):
paul@27 750
paul@27 751
    """
paul@27 752
    Using 'request', return a link to 'page' with the given link 'text' and
paul@27 753
    optional 'query_string'.
paul@27 754
    """
paul@27 755
paul@27 756
    text = wikiutil.escape(text)
paul@27 757
paul@27 758
    if isMoin15():
paul@27 759
        url = wikiutil.quoteWikinameURL(page.page_name)
paul@27 760
        if query_string is not None:
paul@27 761
            url = "%s?%s" % (url, query_string)
paul@27 762
        return wikiutil.link_tag(request, url, text, getattr(page, "formatter", None))
paul@27 763
    else:
paul@27 764
        return page.link_to_raw(request, text, query_string)
paul@27 765
paul@38 766
def getPageURL(request, page):
paul@38 767
paul@38 768
    "Using 'request', return the URL of 'page'."
paul@38 769
paul@38 770
    if isMoin15():
paul@38 771
        return request.getQualifiedURL(page.url(request))
paul@38 772
    else:
paul@38 773
        return request.getQualifiedURL(page.url(request, relative=0))
paul@38 774
paul@10 775
# vim: tabstop=4 expandtab shiftwidth=4