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