EventAggregator

Annotated EventAggregatorSupport.py

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