EventAggregator

Annotated EventAggregatorSupport.py

54:df75a72e54fe
2009-10-30 Paul Boddie Revert previously added link rule, since it probably doesn't do anything extra.
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@51 19
__version__ = "0.4"
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@49 252
            elif term in ("title", "summary"):
paul@47 253
                desc = getEncodedWikiText(event_details[term])
paul@47 254
paul@49 255
            # Text which need not be quoted, but it will be Wiki text.
paul@49 256
paul@49 257
            elif term in ("description",):
paul@49 258
                desc = event_details[term]
paul@49 259
paul@47 260
        new_body_parts.append(desc)
paul@47 261
paul@47 262
    else:
paul@47 263
        new_body_parts.append(body[end_of_last_match:])
paul@47 264
paul@47 265
    return "".join(new_body_parts)
paul@47 266
paul@47 267
def setCategoryMembership(body, category_names):
paul@47 268
paul@47 269
    """
paul@47 270
    Set the category membership in the given page 'body' using the specified
paul@47 271
    'category_names' and returning the new body text.
paul@47 272
    """
paul@47 273
paul@47 274
    match = category_membership_regexp.search(body)
paul@47 275
    if match:
paul@47 276
        return "".join([body[:match.start()], " ".join(category_names), body[match.end():]])
paul@47 277
    else:
paul@47 278
        return body
paul@47 279
paul@19 280
def getEventSummary(event_page, event_details):
paul@19 281
paul@19 282
    """
paul@19 283
    Return either the given title or summary of the event described by the given
paul@19 284
    'event_page', according to the given 'event_details', or return the pretty
paul@19 285
    version of the page name.
paul@19 286
    """
paul@19 287
paul@19 288
    if event_details.has_key("title"):
paul@19 289
        return event_details["title"]
paul@19 290
    elif event_details.has_key("summary"):
paul@19 291
        return event_details["summary"]
paul@19 292
    else:
paul@19 293
        return getPrettyPageName(event_page)
paul@19 294
paul@10 295
def getDate(s):
paul@10 296
paul@10 297
    "Parse the string 's', extracting and returning a date string."
paul@10 298
paul@10 299
    m = date_regexp.search(s)
paul@10 300
    if m:
paul@10 301
        return tuple(map(int, m.groups()))
paul@10 302
    else:
paul@10 303
        return None
paul@10 304
paul@10 305
def getMonth(s):
paul@10 306
paul@10 307
    "Parse the string 's', extracting and returning a month string."
paul@10 308
paul@10 309
    m = month_regexp.search(s)
paul@10 310
    if m:
paul@10 311
        return tuple(map(int, m.groups()))
paul@10 312
    else:
paul@10 313
        return None
paul@10 314
paul@11 315
def getCurrentMonth():
paul@11 316
paul@11 317
    "Return the current month as a (year, month) tuple."
paul@11 318
paul@11 319
    today = datetime.date.today()
paul@11 320
    return (today.year, today.month)
paul@11 321
paul@17 322
def getCurrentYear():
paul@17 323
paul@17 324
    "Return the current year."
paul@17 325
paul@17 326
    today = datetime.date.today()
paul@17 327
    return today.year
paul@17 328
paul@11 329
def monthupdate(date, n):
paul@11 330
paul@11 331
    "Return 'date' updated by 'n' months."
paul@11 332
paul@11 333
    if n < 0:
paul@11 334
        fn = prevmonth
paul@11 335
    else:
paul@11 336
        fn = nextmonth
paul@11 337
paul@11 338
    i = 0
paul@11 339
    while i < abs(n):
paul@11 340
        date = fn(date)
paul@11 341
        i += 1
paul@11 342
        
paul@11 343
    return date
paul@11 344
paul@13 345
def daterange(first, last, step=1):
paul@11 346
paul@13 347
    """
paul@13 348
    Get the range of dates starting at 'first' and ending on 'last', using the
paul@13 349
    specified 'step'.
paul@13 350
    """
paul@11 351
paul@10 352
    results = []
paul@10 353
paul@10 354
    months_only = len(first) == 2
paul@10 355
    start_year = first[0]
paul@10 356
    end_year = last[0]
paul@10 357
paul@11 358
    for year in range(start_year, end_year + step, step):
paul@11 359
        if step == 1 and year < end_year:
paul@10 360
            end_month = 12
paul@11 361
        elif step == -1 and year > end_year:
paul@11 362
            end_month = 1
paul@10 363
        else:
paul@10 364
            end_month = last[1]
paul@10 365
paul@11 366
        if step == 1 and year > start_year:
paul@10 367
            start_month = 1
paul@11 368
        elif step == -1 and year < start_year:
paul@11 369
            start_month = 12
paul@10 370
        else:
paul@10 371
            start_month = first[1]
paul@10 372
paul@11 373
        for month in range(start_month, end_month + step, step):
paul@10 374
            if months_only:
paul@10 375
                results.append((year, month))
paul@10 376
            else:
paul@11 377
                if step == 1 and month < end_month:
paul@10 378
                    _wd, end_day = calendar.monthrange(year, month)
paul@11 379
                elif step == -1 and month > end_month:
paul@11 380
                    end_day = 1
paul@10 381
                else:
paul@10 382
                    end_day = last[2]
paul@10 383
paul@11 384
                if step == 1 and month > start_month:
paul@10 385
                    start_day = 1
paul@11 386
                elif step == -1 and month < start_month:
paul@11 387
                    _wd, start_day = calendar.monthrange(year, month)
paul@10 388
                else:
paul@10 389
                    start_day = first[2]
paul@10 390
paul@11 391
                for day in range(start_day, end_day + step, step):
paul@10 392
                    results.append((year, month, day))
paul@10 393
paul@10 394
    return results
paul@10 395
paul@10 396
def nextdate(date):
paul@11 397
paul@11 398
    "Return the date following the given 'date'."
paul@11 399
paul@10 400
    year, month, day = date
paul@10 401
    _wd, end_day = calendar.monthrange(year, month)
paul@10 402
    if day == end_day:
paul@10 403
        if month == 12:
paul@10 404
            return (year + 1, 1, 1)
paul@10 405
        else:
paul@10 406
            return (year, month + 1, 1)
paul@10 407
    else:
paul@10 408
        return (year, month, day + 1)
paul@10 409
paul@11 410
def prevdate(date):
paul@11 411
paul@11 412
    "Return the date preceding the given 'date'."
paul@11 413
paul@11 414
    year, month, day = date
paul@11 415
    if day == 1:
paul@11 416
        if month == 1:
paul@11 417
            return (year - 1, 12, 31)
paul@11 418
        else:
paul@11 419
            _wd, end_day = calendar.monthrange(year, month - 1)
paul@11 420
            return (year, month - 1, end_day)
paul@11 421
    else:
paul@11 422
        return (year, month, day - 1)
paul@11 423
paul@11 424
def nextmonth(date):
paul@11 425
paul@11 426
    "Return the (year, month) tuple following 'date'."
paul@11 427
paul@11 428
    year, month = date
paul@11 429
    if month == 12:
paul@11 430
        return (year + 1, 1)
paul@11 431
    else:
paul@11 432
        return year, month + 1
paul@11 433
paul@11 434
def prevmonth(date):
paul@11 435
paul@11 436
    "Return the (year, month) tuple preceding 'date'."
paul@11 437
paul@11 438
    year, month = date
paul@11 439
    if month == 1:
paul@11 440
        return (year - 1, 12)
paul@11 441
    else:
paul@11 442
        return year, month - 1
paul@11 443
paul@13 444
def span(start, end):
paul@13 445
paul@13 446
    "Return the difference between 'start' and 'end'."
paul@13 447
paul@13 448
    return end[0] - start[0], end[1] - start[1]
paul@13 449
paul@10 450
def getEvents(request, category_names, calendar_start=None, calendar_end=None):
paul@10 451
paul@10 452
    """
paul@10 453
    Using the 'request', generate a list of events found on pages belonging to
paul@10 454
    the specified 'category_names', using the optional 'calendar_start' and
paul@10 455
    'calendar_end' month tuples of the form (year, month) to indicate a window
paul@10 456
    of interest.
paul@10 457
paul@10 458
    Return a list of events, a dictionary mapping months to event lists (within
paul@10 459
    the window of interest), a list of all events within the window of interest,
paul@10 460
    the earliest month of an event within the window of interest, and the latest
paul@10 461
    month of an event within the window of interest.
paul@10 462
    """
paul@10 463
paul@12 464
    # Re-order the window, if appropriate.
paul@12 465
paul@12 466
    if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
paul@12 467
        calendar_start, calendar_end = calendar_end, calendar_start
paul@12 468
paul@10 469
    events = []
paul@10 470
    shown_events = {}
paul@10 471
    all_shown_events = []
paul@17 472
    processed_pages = set()
paul@10 473
paul@10 474
    earliest = None
paul@10 475
    latest = None
paul@10 476
paul@10 477
    for category_name in category_names:
paul@10 478
paul@10 479
        # Get the pages and page names in the category.
paul@10 480
paul@10 481
        pages_in_category = getPages(category_name, request)
paul@10 482
paul@10 483
        # Visit each page in the category.
paul@10 484
paul@10 485
        for page_in_category in pages_in_category:
paul@10 486
            pagename = page_in_category.page_name
paul@10 487
paul@17 488
            # Only process each page once.
paul@17 489
paul@17 490
            if pagename in processed_pages:
paul@17 491
                continue
paul@17 492
            else:
paul@17 493
                processed_pages.add(pagename)
paul@17 494
paul@10 495
            # Get a real page, not a result page.
paul@10 496
paul@10 497
            real_page_in_category = Page(request, pagename)
paul@10 498
            event_details = getEventDetails(real_page_in_category)
paul@10 499
paul@10 500
            # Define the event as the page together with its details.
paul@10 501
paul@10 502
            event = (real_page_in_category, event_details)
paul@10 503
            events.append(event)
paul@10 504
paul@10 505
            # Test for the suitability of the event.
paul@10 506
paul@10 507
            if event_details.has_key("start") and event_details.has_key("end"):
paul@10 508
paul@10 509
                start_month = event_details["start"][:2]
paul@10 510
                end_month = event_details["end"][:2]
paul@10 511
paul@10 512
                # Compare the months of the dates to the requested calendar
paul@10 513
                # window, if any.
paul@10 514
paul@10 515
                if (calendar_start is None or end_month >= calendar_start) and \
paul@10 516
                    (calendar_end is None or start_month <= calendar_end):
paul@10 517
paul@10 518
                    all_shown_events.append(event)
paul@10 519
paul@10 520
                    if earliest is None or start_month < earliest:
paul@10 521
                        earliest = start_month
paul@10 522
                    if latest is None or end_month > latest:
paul@10 523
                        latest = end_month
paul@10 524
paul@10 525
                    # Store the event in the month-specific dictionary.
paul@10 526
paul@10 527
                    first = max(start_month, calendar_start or start_month)
paul@10 528
                    last = min(end_month, calendar_end or end_month)
paul@10 529
paul@10 530
                    for event_month in daterange(first, last):
paul@10 531
                        if not shown_events.has_key(event_month):
paul@10 532
                            shown_events[event_month] = []
paul@10 533
                        shown_events[event_month].append(event)
paul@10 534
paul@10 535
    return events, shown_events, all_shown_events, earliest, latest
paul@10 536
paul@29 537
def setEventTimestamps(request, events):
paul@29 538
paul@29 539
    """
paul@29 540
    Using 'request', set timestamp details in the details dictionary of each of
paul@29 541
    the 'events': a list of the form (event_page, event_details).
paul@29 542
paul@29 543
    Retutn the latest timestamp found.
paul@29 544
    """
paul@29 545
paul@29 546
    latest = None
paul@29 547
paul@29 548
    for event_page, event_details in events:
paul@29 549
paul@29 550
        # Get the initial revision of the page.
paul@29 551
paul@29 552
        revisions = event_page.getRevList()
paul@29 553
        event_page_initial = Page(request, event_page.page_name, rev=revisions[-1])
paul@29 554
paul@29 555
        # Get the created and last modified times.
paul@29 556
paul@30 557
        initial_revision = getPageRevision(event_page_initial)
paul@30 558
        event_details["created"] = initial_revision["timestamp"]
paul@30 559
        latest_revision = getPageRevision(event_page)
paul@30 560
        event_details["last-modified"] = latest_revision["timestamp"]
paul@29 561
        event_details["sequence"] = len(revisions) - 1
paul@30 562
        event_details["last-comment"] = latest_revision["comment"]
paul@29 563
paul@29 564
        if latest is None or latest < event_details["last-modified"]:
paul@29 565
            latest = event_details["last-modified"]
paul@29 566
paul@29 567
    return latest
paul@29 568
paul@26 569
def compareEvents(event1, event2):
paul@26 570
paul@26 571
    """
paul@26 572
    Compare 'event1' and 'event2' by start and end date, where both parameters
paul@26 573
    are of the following form:
paul@26 574
paul@26 575
    (event_page, event_details)
paul@26 576
    """
paul@26 577
paul@26 578
    event_page1, event_details1 = event1
paul@26 579
    event_page2, event_details2 = event2
paul@26 580
    return cmp(
paul@26 581
        (event_details1["start"], event_details1["end"]),
paul@26 582
        (event_details2["start"], event_details2["end"])
paul@26 583
        )
paul@26 584
paul@26 585
def getOrderedEvents(events):
paul@26 586
paul@26 587
    """
paul@26 588
    Return a list with the given 'events' ordered according to their start and
paul@26 589
    end dates. Each list element must be of the following form:
paul@26 590
paul@26 591
    (event_page, event_details)
paul@26 592
    """
paul@26 593
paul@26 594
    ordered_events = events[:]
paul@26 595
    ordered_events.sort(compareEvents)
paul@26 596
    return ordered_events
paul@26 597
paul@13 598
def getConcretePeriod(calendar_start, calendar_end, earliest, latest):
paul@13 599
paul@13 600
    """
paul@13 601
    From the requested 'calendar_start' and 'calendar_end', which may be None,
paul@13 602
    indicating that no restriction is imposed on the period for each of the
paul@13 603
    boundaries, use the 'earliest' and 'latest' event months to define a
paul@13 604
    specific period of interest.
paul@13 605
    """
paul@13 606
paul@13 607
    # Define the period as starting with any specified start month or the
paul@13 608
    # earliest event known, ending with any specified end month or the latest
paul@13 609
    # event known.
paul@13 610
paul@13 611
    first = calendar_start or earliest
paul@13 612
    last = calendar_end or latest
paul@13 613
paul@13 614
    # If there is no range of months to show, perhaps because there are no
paul@13 615
    # events in the requested period, and there was no start or end month
paul@13 616
    # specified, show only the month indicated by the start or end of the
paul@13 617
    # requested period. If all events were to be shown but none were found show
paul@13 618
    # the current month.
paul@13 619
paul@13 620
    if first is None:
paul@13 621
        first = last or getCurrentMonth()
paul@13 622
    if last is None:
paul@13 623
        last = first or getCurrentMonth()
paul@13 624
paul@13 625
    # Permit "expiring" periods (where the start date approaches the end date).
paul@13 626
paul@13 627
    return min(first, last), last
paul@13 628
paul@15 629
def getCoverage(start, end, events):
paul@15 630
paul@15 631
    """
paul@15 632
    Within the period defined by the 'start' and 'end' dates, determine the
paul@15 633
    coverage of the days in the period by the given 'events', returning a set of
paul@15 634
    covered days, along with a list of slots, where each slot contains a tuple
paul@15 635
    of the form (set of covered days, events).
paul@15 636
    """
paul@15 637
paul@15 638
    all_events = []
paul@15 639
    full_coverage = set()
paul@15 640
paul@15 641
    # Get event details.
paul@15 642
paul@15 643
    for event in events:
paul@15 644
        event_page, event_details = event
paul@15 645
paul@15 646
        # Test for the event in the period.
paul@15 647
paul@15 648
        if event_details["start"] <= end and event_details["end"] >= start:
paul@15 649
paul@15 650
            # Find the coverage of this period for the event.
paul@15 651
paul@15 652
            event_start = max(event_details["start"], start)
paul@15 653
            event_end = min(event_details["end"], end)
paul@15 654
            event_coverage = set(daterange(event_start, event_end))
paul@15 655
paul@15 656
            # Update the overall coverage.
paul@15 657
paul@15 658
            full_coverage.update(event_coverage)
paul@15 659
paul@15 660
            # Try and fit the event into the events list.
paul@15 661
paul@15 662
            for i, (coverage, covered_events) in enumerate(all_events):
paul@15 663
paul@15 664
                # Where the event does not overlap with the current
paul@15 665
                # element, add it alongside existing events.
paul@15 666
paul@15 667
                if not coverage.intersection(event_coverage):
paul@15 668
                    covered_events.append(event)
paul@15 669
                    all_events[i] = coverage.union(event_coverage), covered_events
paul@15 670
                    break
paul@15 671
paul@15 672
            # Make a new element in the list if the event cannot be
paul@15 673
            # marked alongside existing events.
paul@15 674
paul@15 675
            else:
paul@15 676
                all_events.append((event_coverage, [event]))
paul@15 677
paul@15 678
    return full_coverage, all_events
paul@15 679
paul@19 680
# User interface functions.
paul@19 681
paul@23 682
def getParameter(request, name):
paul@23 683
    return request.form.get(name, [None])[0]
paul@23 684
paul@19 685
def getParameterMonth(arg):
paul@19 686
    n = None
paul@19 687
paul@19 688
    if arg.startswith("current"):
paul@19 689
        date = getCurrentMonth()
paul@19 690
        if len(arg) > 8:
paul@19 691
            n = int(arg[7:])
paul@19 692
paul@19 693
    elif arg.startswith("yearstart"):
paul@19 694
        date = (getCurrentYear(), 1)
paul@19 695
        if len(arg) > 10:
paul@19 696
            n = int(arg[9:])
paul@19 697
paul@19 698
    elif arg.startswith("yearend"):
paul@19 699
        date = (getCurrentYear(), 12)
paul@19 700
        if len(arg) > 8:
paul@19 701
            n = int(arg[7:])
paul@19 702
paul@19 703
    else:
paul@19 704
        date = getMonth(arg)
paul@19 705
paul@19 706
    if n is not None:
paul@19 707
        date = monthupdate(date, n)
paul@19 708
paul@19 709
    return date
paul@19 710
paul@19 711
def getFormMonth(request, calendar_name, argname):
paul@19 712
    if calendar_name is None:
paul@19 713
        calendar_prefix = argname
paul@19 714
    else:
paul@19 715
        calendar_prefix = "%s-%s" % (calendar_name, argname)
paul@19 716
paul@23 717
    arg = getParameter(request, calendar_prefix)
paul@19 718
    if arg is not None:
paul@19 719
        return getParameterMonth(arg)
paul@19 720
    else:
paul@19 721
        return None
paul@19 722
paul@23 723
def getFormMonthPair(request, yeararg, montharg):
paul@23 724
    year = getParameter(request, yeararg)
paul@23 725
    month = getParameter(request, montharg)
paul@23 726
    if year and month:
paul@23 727
        return (int(year), int(month))
paul@23 728
    else:
paul@23 729
        return None
paul@23 730
paul@19 731
def getPrettyPageName(page):
paul@19 732
paul@19 733
    "Return a nicely formatted title/name for the given 'page'."
paul@19 734
paul@27 735
    if isMoin15():
paul@27 736
        title = page.split_title(page.request, force=1)
paul@27 737
    else:
paul@27 738
        title = page.split_title(force=1)
paul@27 739
paul@27 740
    return title.replace("_", " ").replace("/", u" ? ")
paul@19 741
paul@22 742
def getMonthLabel(month):
paul@22 743
paul@22 744
    "Return an unlocalised label for the given 'month'."
paul@22 745
paul@22 746
    return month_labels[month - 1] # zero-based labels
paul@22 747
paul@22 748
def getDayLabel(weekday):
paul@22 749
paul@22 750
    "Return an unlocalised label for the given 'weekday'."
paul@22 751
paul@22 752
    return weekday_labels[weekday]
paul@22 753
paul@27 754
def linkToPage(request, page, text, query_string=None):
paul@27 755
paul@27 756
    """
paul@27 757
    Using 'request', return a link to 'page' with the given link 'text' and
paul@27 758
    optional 'query_string'.
paul@27 759
    """
paul@27 760
paul@27 761
    text = wikiutil.escape(text)
paul@27 762
paul@27 763
    if isMoin15():
paul@27 764
        url = wikiutil.quoteWikinameURL(page.page_name)
paul@27 765
        if query_string is not None:
paul@27 766
            url = "%s?%s" % (url, query_string)
paul@27 767
        return wikiutil.link_tag(request, url, text, getattr(page, "formatter", None))
paul@27 768
    else:
paul@27 769
        return page.link_to_raw(request, text, query_string)
paul@27 770
paul@38 771
def getPageURL(request, page):
paul@38 772
paul@38 773
    "Using 'request', return the URL of 'page'."
paul@38 774
paul@38 775
    if isMoin15():
paul@38 776
        return request.getQualifiedURL(page.url(request))
paul@38 777
    else:
paul@38 778
        return request.getQualifiedURL(page.url(request, relative=0))
paul@38 779
paul@10 780
# vim: tabstop=4 expandtab shiftwidth=4