1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator library 4 5 @copyright: 2008, 2009, 2010 by Paul Boddie <paul@boddie.org.uk> 6 @copyright: 2000-2004 Juergen Hermann <jh@web.de>, 7 2005-2008 MoinMoin:ThomasWaldmann. 8 @license: GNU GPL (v2 or later), see COPYING.txt for details. 9 """ 10 11 from MoinMoin.Page import Page 12 from MoinMoin import search, version 13 from MoinMoin import wikiutil 14 import calendar 15 import datetime 16 import time 17 import re 18 19 try: 20 set 21 except NameError: 22 from sets import Set as set 23 24 __version__ = "0.5" 25 26 # Date labels. 27 28 month_labels = ["January", "February", "March", "April", "May", "June", 29 "July", "August", "September", "October", "November", "December"] 30 weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 31 32 # Regular expressions where MoinMoin does not provide the required support. 33 34 category_regexp = None 35 36 # Page parsing. 37 38 definition_list_regexp = re.compile(ur'(?P<wholeterm>^(?P<optcomment>#*)\s+(?P<term>.*?)::\s)(?P<desc>.*?)$', re.UNICODE | re.MULTILINE) 39 category_membership_regexp = re.compile(ur"^\s*((Category\S+)(\s+Category\S+)*)\s*$", re.MULTILINE | re.UNICODE) 40 41 # Value parsing. 42 43 date_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})', re.UNICODE) 44 month_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})', re.UNICODE) 45 verbatim_regexp = re.compile(ur'(?:' 46 ur'<<Verbatim\((?P<verbatim>.*?)\)>>' 47 ur'|' 48 ur'\[\[Verbatim\((?P<verbatim2>.*?)\)\]\]' 49 ur'|' 50 ur'`(?P<monospace>.*?)`' 51 ur'|' 52 ur'{{{(?P<preformatted>.*?)}}}' 53 ur')', re.UNICODE) 54 55 # Utility functions. 56 57 def isMoin15(): 58 return version.release.startswith("1.5.") 59 60 def getCategoryPattern(request): 61 global category_regexp 62 63 try: 64 return request.cfg.cache.page_category_regexact 65 except AttributeError: 66 67 # Use regular expression from MoinMoin 1.7.1 otherwise. 68 69 if category_regexp is None: 70 category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE) 71 return category_regexp 72 73 # Textual representations. 74 75 def getHTTPTimeString(tmtuple): 76 return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ( 77 weekday_labels[tmtuple.tm_wday], 78 tmtuple.tm_mday, 79 month_labels[tmtuple.tm_mon -1], # zero-based labels 80 tmtuple.tm_year, 81 tmtuple.tm_hour, 82 tmtuple.tm_min, 83 tmtuple.tm_sec 84 ) 85 86 def getSimpleWikiText(text): 87 88 """ 89 Return the plain text representation of the given 'text' which may employ 90 certain Wiki syntax features, such as those providing verbatim or monospaced 91 text. 92 """ 93 94 # NOTE: Re-implementing support for verbatim text and linking avoidance. 95 96 return "".join([s for s in verbatim_regexp.split(text) if s is not None]) 97 98 def getEncodedWikiText(text): 99 100 "Encode the given 'text' in a verbatim representation." 101 102 return "<<Verbatim(%s)>>" % text 103 104 def getPrettyTitle(title): 105 106 "Return a nicely formatted version of the given 'title'." 107 108 return title.replace("_", " ").replace("/", u" ? ") 109 110 def getMonthLabel(month): 111 112 "Return an unlocalised label for the given 'month'." 113 114 return month_labels[month - 1] # zero-based labels 115 116 def getDayLabel(weekday): 117 118 "Return an unlocalised label for the given 'weekday'." 119 120 return weekday_labels[weekday] 121 122 # Action support functions. 123 124 def getPageRevision(page): 125 126 "Return the revision details dictionary for the given 'page'." 127 128 # From Page.edit_info... 129 130 if hasattr(page, "editlog_entry"): 131 line = page.editlog_entry() 132 else: 133 line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x 134 135 timestamp = line.ed_time_usecs 136 mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x 137 return {"timestamp" : time.gmtime(mtime), "comment" : line.comment} 138 139 # Category discovery and searching. 140 141 def getCategories(request): 142 143 """ 144 From the AdvancedSearch macro, return a list of category page names using 145 the given 'request'. 146 """ 147 148 # This will return all pages with "Category" in the title. 149 150 cat_filter = getCategoryPattern(request).search 151 return request.rootpage.getPageList(filter=cat_filter) 152 153 def getCategoryMapping(category_pagenames, request): 154 155 """ 156 For the given 'category_pagenames' return a list of tuples of the form 157 (category name, category page name) using the given 'request'. 158 """ 159 160 cat_pattern = getCategoryPattern(request) 161 mapping = [] 162 for pagename in category_pagenames: 163 name = cat_pattern.match(pagename).group("key") 164 if name != "Category": 165 mapping.append((name, pagename)) 166 mapping.sort() 167 return mapping 168 169 def getCategoryPages(pagename, request): 170 171 """ 172 Return the pages associated with the given category 'pagename' using the 173 'request'. 174 """ 175 176 query = search.QueryParser().parse_query('category:%s' % pagename) 177 if isMoin15(): 178 results = search.searchPages(request, query) 179 results.sortByPagename() 180 else: 181 results = search.searchPages(request, query, "page_name") 182 183 cat_pattern = getCategoryPattern(request) 184 pages = [] 185 for page in results.hits: 186 if not cat_pattern.match(page.page_name): 187 pages.append(page) 188 return pages 189 190 # The main activity functions. 191 192 class EventPage: 193 194 "An event page." 195 196 def __init__(self, page): 197 self.page = page 198 self.events = None 199 self.body = None 200 self.categories = None 201 202 def copyPage(self, page): 203 204 "Copy the body of the given 'page'." 205 206 self.body = page.getBody() 207 208 def getPageURL(self, request): 209 210 "Using 'request', return the URL of this page." 211 212 page = self.page 213 214 if isMoin15(): 215 return request.getQualifiedURL(page.url(request)) 216 else: 217 return request.getQualifiedURL(page.url(request, relative=0)) 218 219 def getFormat(self): 220 221 "Get the format used on this page." 222 223 if isMoin15(): 224 return "wiki" # page.pi_format 225 else: 226 return self.page.pi["format"] 227 228 def getRevisions(self): 229 230 "Return a list of page revisions." 231 232 return self.page.getRevList() 233 234 def getPageRevision(self): 235 236 "Return the revision details dictionary for this page." 237 238 return getPageRevision(self.page) 239 240 def getPageName(self): 241 242 "Return the page name." 243 244 return self.page.page_name 245 246 def getPrettyPageName(self): 247 248 "Return a nicely formatted title/name for this page." 249 250 return getPrettyPageName(self.page) 251 252 def getBody(self): 253 254 "Get the current page body." 255 256 if self.body is None: 257 self.body = self.page.get_raw_body() 258 return self.body 259 260 def getEvents(self): 261 262 "Return a list of events from this page." 263 264 if self.events is None: 265 details = {} 266 self.events = [Event(self, details)] 267 268 if self.getFormat() == "wiki": 269 for match in definition_list_regexp.finditer(self.getBody()): 270 271 # Skip commented-out items. 272 273 if match.group("optcomment"): 274 continue 275 276 # Permit case-insensitive list terms. 277 278 term = match.group("term").lower() 279 desc = match.group("desc") 280 281 # Special value type handling. 282 283 # Dates. 284 285 if term in ("start", "end"): 286 desc = getDate(desc) 287 288 # Lists (whose elements may be quoted). 289 290 elif term in ("topics", "categories"): 291 desc = [getSimpleWikiText(value.strip()) for value in desc.split(",")] 292 293 # Labels which may well be quoted. 294 295 elif term in ("title", "summary", "description"): 296 desc = getSimpleWikiText(desc) 297 298 if desc is not None: 299 300 # Handle apparent duplicates by creating a new set of 301 # details. 302 303 if details.has_key(term): 304 details = {} 305 self.events.append(Event(self, details)) 306 307 details[term] = desc 308 309 return self.events 310 311 def setEvents(self, events): 312 313 "Set the given 'events' on this page." 314 315 self.events = events 316 317 def getCategoryMembership(self): 318 319 "Get the category names from this page." 320 321 if self.categories is None: 322 body = self.getBody() 323 match = category_membership_regexp.search(body) 324 self.categories = match.findall().split() 325 326 return self.categories 327 328 def setCategoryMembership(self, category_names): 329 330 """ 331 Set the category membership for the page using the specified 332 'category_names'. 333 """ 334 335 self.categories = category_names 336 337 def flushEventDetails(self): 338 339 "Flush the current event details to this page's body text." 340 341 new_body_parts = [] 342 end_of_last_match = 0 343 body = self.getBody() 344 345 events = iter(self.getEvents()) 346 347 event = events.next() 348 event_details = event.getDetails() 349 replaced_terms = set() 350 351 for match in definition_list_regexp.finditer(body): 352 353 # Permit case-insensitive list terms. 354 355 term = match.group("term").lower() 356 desc = match.group("desc") 357 358 # Check that the term has not already been substituted. If so, 359 # get the next event. 360 361 if term in replaced_terms: 362 try: 363 event = events.next() 364 365 # No more events. 366 367 except StopIteration: 368 break 369 370 event_details = event.getDetails() 371 replaced_terms = set() 372 373 # Add preceding text to the new body. 374 375 new_body_parts.append(body[end_of_last_match:match.start()]) 376 377 # Get the matching regions, adding the term to the new body. 378 379 new_body_parts.append(match.group("wholeterm")) 380 381 # Special value type handling. 382 383 if event_details.has_key(term): 384 385 # Dates. 386 387 if term in ("start", "end"): 388 desc = desc.replace("YYYY-MM-DD", str(event_details[term])) 389 390 # Lists (whose elements may be quoted). 391 392 elif term in ("topics", "categories"): 393 desc = ", ".join(getEncodedWikiText(event_details[term])) 394 395 # Labels which may well be quoted. 396 397 elif term in ("title", "summary"): 398 desc = getEncodedWikiText(event_details[term]) 399 400 # Text which need not be quoted, but it will be Wiki text. 401 402 elif term in ("description",): 403 desc = event_details[term] 404 405 replaced_terms.add(term) 406 407 # Add the replaced value. 408 409 new_body_parts.append(desc) 410 411 # Remember where in the page has been processed. 412 413 end_of_last_match = match.end() 414 415 # Write the rest of the page. 416 417 new_body_parts.append(body[end_of_last_match:]) 418 419 self.body = "".join(new_body_parts) 420 421 def flushCategoryMembership(self): 422 423 "Flush the category membership to the page body." 424 425 body = self.getBody() 426 category_names = self.getCategoryMembership() 427 match = category_membership_regexp.search(body) 428 429 if match: 430 self.body = "".join([body[:match.start()], " ".join(category_names), body[match.end():]]) 431 432 def saveChanges(self): 433 434 "Save changes to the event." 435 436 self.flushEventDetails() 437 self.flushCategoryMembership() 438 self.page.saveText(self.getBody(), 0) 439 440 def linkToPage(self, request, text, query_string=None): 441 442 """ 443 Using 'request', return a link to this page with the given link 'text' 444 and optional 'query_string'. 445 """ 446 447 return linkToPage(request, self.page, text, query_string) 448 449 class Event: 450 451 "A description of an event." 452 453 def __init__(self, page, details): 454 self.page = page 455 self.details = details 456 457 def __cmp__(self, other): 458 459 """ 460 Compare this object with 'other' using the event start and end details. 461 """ 462 463 details1 = self.details 464 details2 = other.details 465 return cmp( 466 (details1["start"], details1["end"]), 467 (details2["start"], details2["end"]) 468 ) 469 470 def getPage(self): 471 472 "Return the page describing this event." 473 474 return self.page 475 476 def setPage(self, page): 477 478 "Set the 'page' describing this event." 479 480 self.page = page 481 482 def getSummary(self, event_parent=None): 483 484 """ 485 Return either the given title or summary of the event according to the 486 event details, or a summary made from using the pretty version of the 487 page name. 488 489 If the optional 'event_parent' is specified, any page beneath the given 490 'event_parent' page in the page hierarchy will omit this parent information 491 if its name is used as the summary. 492 """ 493 494 event_details = self.details 495 496 if event_details.has_key("title"): 497 return event_details["title"] 498 elif event_details.has_key("summary"): 499 return event_details["summary"] 500 else: 501 # If appropriate, remove the parent details and "/" character. 502 503 title = self.page.getPageName() 504 505 if event_parent and title.startswith(event_parent): 506 title = title[len(event_parent.rstrip("/")) + 1:] 507 508 return getPrettyTitle(title) 509 510 def getDetails(self): 511 512 "Return the details for this event." 513 514 return self.details 515 516 def setDetails(self, event_details): 517 518 "Set the 'event_details' for this event." 519 520 self.details = event_details 521 522 def getEvents(request, category_names, calendar_start=None, calendar_end=None): 523 524 """ 525 Using the 'request', generate a list of events found on pages belonging to 526 the specified 'category_names', using the optional 'calendar_start' and 527 'calendar_end' month tuples of the form (year, month) to indicate a window 528 of interest. 529 530 Return a list of events, a dictionary mapping months to event lists (within 531 the window of interest), a list of all events within the window of interest, 532 the earliest month of an event within the window of interest, and the latest 533 month of an event within the window of interest. 534 """ 535 536 # Re-order the window, if appropriate. 537 538 if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end: 539 calendar_start, calendar_end = calendar_end, calendar_start 540 541 events = [] 542 shown_events = {} 543 all_shown_events = [] 544 processed_pages = set() 545 546 earliest = None 547 latest = None 548 549 for category_name in category_names: 550 551 # Get the pages and page names in the category. 552 553 pages_in_category = getCategoryPages(category_name, request) 554 555 # Visit each page in the category. 556 557 for page_in_category in pages_in_category: 558 pagename = page_in_category.page_name 559 560 # Only process each page once. 561 562 if pagename in processed_pages: 563 continue 564 else: 565 processed_pages.add(pagename) 566 567 # Get a real page, not a result page. 568 569 event_page = EventPage(Page(request, pagename)) 570 571 # Get all events described in the page. 572 573 for event in event_page.getEvents(): 574 event_details = event.getDetails() 575 576 # Remember the event. 577 578 events.append(event) 579 580 # Test for the suitability of the event. 581 582 if event_details.has_key("start") and event_details.has_key("end"): 583 584 start_month = event_details["start"].as_month() 585 end_month = event_details["end"].as_month() 586 587 # Compare the months of the dates to the requested calendar 588 # window, if any. 589 590 if (calendar_start is None or end_month >= calendar_start) and \ 591 (calendar_end is None or start_month <= calendar_end): 592 593 all_shown_events.append(event) 594 595 if earliest is None or start_month < earliest: 596 earliest = start_month 597 if latest is None or end_month > latest: 598 latest = end_month 599 600 # Store the event in the month-specific dictionary. 601 602 first = max(start_month, calendar_start or start_month) 603 last = min(end_month, calendar_end or end_month) 604 605 for event_month in first.months_until(last): 606 if not shown_events.has_key(event_month): 607 shown_events[event_month] = [] 608 shown_events[event_month].append(event) 609 610 return events, shown_events, all_shown_events, earliest, latest 611 612 def setEventTimestamps(request, events): 613 614 """ 615 Using 'request', set timestamp details in the details dictionary of each of 616 the 'events'. 617 618 Retutn the latest timestamp found. 619 """ 620 621 latest = None 622 623 for event in events: 624 event_details = event.getDetails() 625 event_page = event.getPage() 626 627 # Get the initial revision of the page. 628 629 revisions = event_page.getRevisions() 630 event_page_initial = Page(request, event_page.getPageName(), rev=revisions[-1]) 631 632 # Get the created and last modified times. 633 634 initial_revision = getPageRevision(event_page_initial) 635 event_details["created"] = initial_revision["timestamp"] 636 latest_revision = event_page.getPageRevision() 637 event_details["last-modified"] = latest_revision["timestamp"] 638 event_details["sequence"] = len(revisions) - 1 639 event_details["last-comment"] = latest_revision["comment"] 640 641 if latest is None or latest < event_details["last-modified"]: 642 latest = event_details["last-modified"] 643 644 return latest 645 646 def getOrderedEvents(events): 647 648 """ 649 Return a list with the given 'events' ordered according to their start and 650 end dates. 651 """ 652 653 ordered_events = events[:] 654 ordered_events.sort() 655 return ordered_events 656 657 def getConcretePeriod(calendar_start, calendar_end, earliest, latest): 658 659 """ 660 From the requested 'calendar_start' and 'calendar_end', which may be None, 661 indicating that no restriction is imposed on the period for each of the 662 boundaries, use the 'earliest' and 'latest' event months to define a 663 specific period of interest. 664 """ 665 666 # Define the period as starting with any specified start month or the 667 # earliest event known, ending with any specified end month or the latest 668 # event known. 669 670 first = calendar_start or earliest 671 last = calendar_end or latest 672 673 # If there is no range of months to show, perhaps because there are no 674 # events in the requested period, and there was no start or end month 675 # specified, show only the month indicated by the start or end of the 676 # requested period. If all events were to be shown but none were found show 677 # the current month. 678 679 if first is None: 680 first = last or getCurrentMonth() 681 if last is None: 682 last = first or getCurrentMonth() 683 684 # Permit "expiring" periods (where the start date approaches the end date). 685 686 return min(first, last), last 687 688 def getCoverage(start, end, events): 689 690 """ 691 Within the period defined by the 'start' and 'end' dates, determine the 692 coverage of the days in the period by the given 'events', returning a set of 693 covered days, along with a list of slots, where each slot contains a tuple 694 of the form (set of covered days, events). 695 """ 696 697 all_events = [] 698 full_coverage = set() 699 700 # Get event details. 701 702 for event in events: 703 event_details = event.getDetails() 704 705 # Test for the event in the period. 706 707 if event_details["start"] <= end and event_details["end"] >= start: 708 709 # Find the coverage of this period for the event. 710 711 event_start = max(event_details["start"], start) 712 event_end = min(event_details["end"], end) 713 event_coverage = set(event_start.days_until(event_end)) 714 715 # Update the overall coverage. 716 717 full_coverage.update(event_coverage) 718 719 # Try and fit the event into the events list. 720 721 for i, (coverage, covered_events) in enumerate(all_events): 722 723 # Where the event does not overlap with the current 724 # element, add it alongside existing events. 725 726 if not coverage.intersection(event_coverage): 727 covered_events.append(event) 728 all_events[i] = coverage.union(event_coverage), covered_events 729 break 730 731 # Make a new element in the list if the event cannot be 732 # marked alongside existing events. 733 734 else: 735 all_events.append((event_coverage, [event])) 736 737 return full_coverage, all_events 738 739 # Date-related functions. 740 741 class Period: 742 743 "A simple period of time." 744 745 def __init__(self, data): 746 self.data = data 747 748 def months(self): 749 return self.data[0] * 12 + self.data[1] 750 751 class Month: 752 753 "A simple year-month representation." 754 755 def __init__(self, data): 756 self.data = tuple(data) 757 758 def __repr__(self): 759 return "%s(%r)" % (self.__class__.__name__, self.data) 760 761 def __str__(self): 762 return "%04d-%02d" % self.as_tuple()[:2] 763 764 def __hash__(self): 765 return hash(self.as_tuple()) 766 767 def as_tuple(self): 768 return self.data 769 770 def as_date(self, day): 771 return Date(self.as_tuple() + (day,)) 772 773 def year(self): 774 return self.data[0] 775 776 def month(self): 777 return self.data[1] 778 779 def month_properties(self): 780 781 """ 782 Return the weekday of the 1st of the month, along with the number of 783 days, as a tuple. 784 """ 785 786 year, month = self.data 787 return calendar.monthrange(year, month) 788 789 def month_update(self, n=1): 790 791 "Return the month updated by 'n' months." 792 793 year, month = self.data 794 return Month((year + (month - 1 + n) / 12, (month - 1 + n) % 12 + 1)) 795 796 def next_month(self): 797 798 "Return the month following this one." 799 800 return self.month_update(1) 801 802 def previous_month(self): 803 804 "Return the month preceding this one." 805 806 return self.month_update(-1) 807 808 def __sub__(self, start): 809 810 """ 811 Return the difference in years and months between this month and the 812 'start' month as a period. 813 """ 814 815 return Period([(x - y) for x, y in zip(self.data, start.data)]) 816 817 def __cmp__(self, other): 818 return cmp(self.data, other.data) 819 820 def until(self, end, nextfn, prevfn): 821 month = self 822 months = [month] 823 if month < end: 824 while month < end: 825 month = nextfn(month) 826 months.append(month) 827 elif month > end: 828 while month > end: 829 month = prevfn(month) 830 months.append(month) 831 return months 832 833 def months_until(self, end): 834 return self.until(end, Month.next_month, Month.previous_month) 835 836 class Date(Month): 837 838 "A simple year-month-day representation." 839 840 def __str__(self): 841 return "%04d-%02d-%02d" % self.as_tuple()[:3] 842 843 def as_month(self): 844 return Month(self.data[:2]) 845 846 def day(self): 847 return self.data[2] 848 849 def next_day(self): 850 851 "Return the date following this one." 852 853 year, month, day = self.data 854 _wd, end_day = calendar.monthrange(year, month) 855 if day == end_day: 856 if month == 12: 857 return Date((year + 1, 1, 1)) 858 else: 859 return Date((year, month + 1, 1)) 860 else: 861 return Date((year, month, day + 1)) 862 863 def previous_day(self): 864 865 "Return the date preceding this one." 866 867 year, month, day = self.data 868 if day == 1: 869 if month == 1: 870 return Date((year - 1, 12, 31)) 871 else: 872 _wd, end_day = calendar.monthrange(year, month - 1) 873 return Date((year, month - 1, end_day)) 874 else: 875 return Date((year, month, day - 1)) 876 877 def days_until(self, end): 878 return self.until(end, Date.next_day, Date.previous_day) 879 880 def getDate(s): 881 882 "Parse the string 's', extracting and returning a date string." 883 884 m = date_regexp.search(s) 885 if m: 886 return Date(map(int, m.groups())) 887 else: 888 return None 889 890 def getMonth(s): 891 892 "Parse the string 's', extracting and returning a month string." 893 894 m = month_regexp.search(s) 895 if m: 896 return Month(map(int, m.groups())) 897 else: 898 return None 899 900 def getCurrentMonth(): 901 902 "Return the current month as a (year, month) tuple." 903 904 today = datetime.date.today() 905 return Month((today.year, today.month)) 906 907 def getCurrentYear(): 908 909 "Return the current year." 910 911 today = datetime.date.today() 912 return today.year 913 914 # User interface functions. 915 916 def getParameter(request, name, default=None): 917 return request.form.get(name, [default])[0] 918 919 def getQualifiedParameter(request, calendar_name, argname, default=None): 920 argname = getQualifiedParameterName(calendar_name, argname) 921 return getParameter(request, argname, default) 922 923 def getQualifiedParameterName(calendar_name, argname): 924 if calendar_name is None: 925 return argname 926 else: 927 return "%s-%s" % (calendar_name, argname) 928 929 def getParameterMonth(arg): 930 931 "Interpret 'arg', recognising keywords and simple arithmetic operations." 932 933 n = None 934 935 if arg.startswith("current"): 936 date = getCurrentMonth() 937 if len(arg) > 8: 938 n = int(arg[7:]) 939 940 elif arg.startswith("yearstart"): 941 date = Month((getCurrentYear(), 1)) 942 if len(arg) > 10: 943 n = int(arg[9:]) 944 945 elif arg.startswith("yearend"): 946 date = Month((getCurrentYear(), 12)) 947 if len(arg) > 8: 948 n = int(arg[7:]) 949 950 else: 951 date = getMonth(arg) 952 953 if n is not None: 954 date = date.month_update(n) 955 956 return date 957 958 def getFormMonth(request, calendar_name, argname): 959 960 """ 961 Return the month from the 'request' for the calendar with the given 962 'calendar_name' using the parameter having the given 'argname'. 963 """ 964 965 arg = getQualifiedParameter(request, calendar_name, argname) 966 if arg is not None: 967 return getParameterMonth(arg) 968 else: 969 return None 970 971 def getFormMonthPair(request, yeararg, montharg): 972 973 """ 974 Return the month from the 'request' for the calendar with the given 975 'calendar_name' using the parameters having the given 'yeararg' and 976 'montharg' names. 977 """ 978 979 year = getParameter(request, yeararg) 980 month = getParameter(request, montharg) 981 if year and month: 982 return Month((int(year), int(month))) 983 else: 984 return None 985 986 # Page-related functions. 987 988 def getPrettyPageName(page): 989 990 "Return a nicely formatted title/name for the given 'page'." 991 992 if isMoin15(): 993 title = page.split_title(page.request, force=1) 994 else: 995 title = page.split_title(force=1) 996 997 return getPrettyTitle(title) 998 999 def linkToPage(request, page, text, query_string=None): 1000 1001 """ 1002 Using 'request', return a link to 'page' with the given link 'text' and 1003 optional 'query_string'. 1004 """ 1005 1006 text = wikiutil.escape(text) 1007 1008 if isMoin15(): 1009 url = wikiutil.quoteWikinameURL(page.page_name) 1010 if query_string is not None: 1011 url = "%s?%s" % (url, query_string) 1012 return wikiutil.link_tag(request, url, text, getattr(page, "formatter", None)) 1013 else: 1014 return page.link_to_raw(request, text, query_string) 1015 1016 # vim: tabstop=4 expandtab shiftwidth=4