EventAggregator

Annotated EventAggregatorSupport.py

48:8d69ccf101db
2009-06-07 Paul Boddie Fixed form HTML and added title and category value retention across submissions. Simplified the date processing, producing errors for obvious fault conditions.
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