1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - MoinSupport library (derived from EventAggregatorSupport) 4 5 @copyright: 2008, 2009, 2010, 2011, 2012 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 DateSupport import * 12 from MoinMoin.Page import Page 13 from MoinMoin import config, search, wikiutil 14 from StringIO import StringIO 15 from shlex import shlex 16 import re 17 import time 18 19 __version__ = "0.2" 20 21 # Content type parsing. 22 23 encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?' 24 encoding_regexp = re.compile(encoding_regexp_str) 25 26 # Accept header parsing. 27 28 accept_regexp_str = ur';\s*q=' 29 accept_regexp = re.compile(accept_regexp_str) 30 31 # Extraction of shared fragments. 32 33 marker_regexp_str = r"([{]{3,}|[}]{3,})" 34 marker_regexp = re.compile(marker_regexp_str, re.MULTILINE | re.DOTALL) # {{{... or }}}... 35 36 # Category extraction from pages. 37 38 category_regexp = None 39 40 # Simple content parsing. 41 42 verbatim_regexp = re.compile(ur'(?:' 43 ur'<<Verbatim\((?P<verbatim>.*?)\)>>' 44 ur'|' 45 ur'\[\[Verbatim\((?P<verbatim2>.*?)\)\]\]' 46 ur'|' 47 ur'!(?P<verbatim3>.*?)(\s|$)?' 48 ur'|' 49 ur'`(?P<monospace>.*?)`' 50 ur'|' 51 ur'{{{(?P<preformatted>.*?)}}}' 52 ur')', re.UNICODE) 53 54 # Category discovery. 55 56 def getCategoryPattern(request): 57 global category_regexp 58 59 try: 60 return request.cfg.cache.page_category_regexact 61 except AttributeError: 62 63 # Use regular expression from MoinMoin 1.7.1 otherwise. 64 65 if category_regexp is None: 66 category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE) 67 return category_regexp 68 69 def getCategories(request): 70 71 """ 72 From the AdvancedSearch macro, return a list of category page names using 73 the given 'request'. 74 """ 75 76 # This will return all pages with "Category" in the title. 77 78 cat_filter = getCategoryPattern(request).search 79 return request.rootpage.getPageList(filter=cat_filter) 80 81 def getCategoryMapping(category_pagenames, request): 82 83 """ 84 For the given 'category_pagenames' return a list of tuples of the form 85 (category name, category page name) using the given 'request'. 86 """ 87 88 cat_pattern = getCategoryPattern(request) 89 mapping = [] 90 for pagename in category_pagenames: 91 name = cat_pattern.match(pagename).group("key") 92 if name != "Category": 93 mapping.append((name, pagename)) 94 mapping.sort() 95 return mapping 96 97 def getCategoryPages(pagename, request): 98 99 """ 100 Return the pages associated with the given category 'pagename' using the 101 'request'. 102 """ 103 104 query = search.QueryParser().parse_query('category:%s' % pagename) 105 results = search.searchPages(request, query, "page_name") 106 return filterCategoryPages(results, request) 107 108 def filterCategoryPages(results, request): 109 110 "Filter category pages from the given 'results' using the 'request'." 111 112 cat_pattern = getCategoryPattern(request) 113 pages = [] 114 for page in results.hits: 115 if not cat_pattern.match(page.page_name): 116 pages.append(page) 117 return pages 118 119 def getAllCategoryPages(category_names, request): 120 121 """ 122 Return all pages belonging to the categories having the given 123 'category_names', using the given 'request'. 124 """ 125 126 pages = [] 127 pagenames = set() 128 129 for category_name in category_names: 130 131 # Get the pages and page names in the category. 132 133 pages_in_category = getCategoryPages(category_name, request) 134 135 # Visit each page in the category. 136 137 for page_in_category in pages_in_category: 138 pagename = page_in_category.page_name 139 140 # Only process each page once. 141 142 if pagename in pagenames: 143 continue 144 else: 145 pagenames.add(pagename) 146 147 pages.append(page_in_category) 148 149 return pages 150 151 def getPagesForSearch(search_pattern, request): 152 153 """ 154 Return result pages for a search employing the given 'search_pattern' and 155 using the given 'request'. 156 """ 157 158 query = search.QueryParser().parse_query(search_pattern) 159 results = search.searchPages(request, query, "page_name") 160 return filterCategoryPages(results, request) 161 162 # WikiDict functions. 163 164 def getWikiDict(pagename, request): 165 166 """ 167 Return the WikiDict provided by the given 'pagename' using the given 168 'request'. 169 """ 170 171 if pagename and Page(request, pagename).exists() and request.user.may.read(pagename): 172 if hasattr(request.dicts, "dict"): 173 return request.dicts.dict(pagename) 174 else: 175 return request.dicts[pagename] 176 else: 177 return None 178 179 # Searching-related functions. 180 181 def getPagesFromResults(result_pages, request): 182 183 "Return genuine pages for the given 'result_pages' using the 'request'." 184 185 return [Page(request, page.page_name) for page in result_pages] 186 187 # Region/section parsing. 188 189 def getRegions(s, include_non_regions=False): 190 191 """ 192 Parse the string 's', returning a list of explicitly declared regions. 193 194 If 'include_non_regions' is specified as a true value, fragments will be 195 included for text between explicitly declared regions. 196 """ 197 198 regions = [] 199 marker = None 200 is_block = True 201 202 # Start a region for exposed text, if appropriate. 203 204 if include_non_regions: 205 regions.append("") 206 207 for match_text in marker_regexp.split(s): 208 209 # Capture section text. 210 211 if is_block: 212 if marker or include_non_regions: 213 regions[-1] += match_text 214 215 # Handle section markers. 216 217 elif not is_block: 218 219 # Close any open sections, returning to exposed text regions. 220 221 if marker: 222 if match_text.startswith("}") and len(marker) == len(match_text): 223 marker = None 224 225 # Start a region for exposed text, if appropriate. 226 227 if include_non_regions: 228 regions.append("") 229 230 # Without a current marker, start a section if an appropriate marker 231 # is given. 232 233 elif match_text.startswith("{"): 234 marker = match_text 235 regions.append("") 236 237 # Markers and section text are added to the current region. 238 239 regions[-1] += match_text 240 241 # The match text alternates between text between markers and the markers 242 # themselves. 243 244 is_block = not is_block 245 246 return regions 247 248 def getFragmentsFromRegions(regions): 249 250 """ 251 Return fragments from the given 'regions', each having the form 252 (format, arguments, body text). 253 """ 254 255 fragments = [] 256 257 for region in regions: 258 if region.startswith("{{{"): 259 260 body = region.lstrip("{").rstrip("}").lstrip() 261 262 # Remove any prelude and process metadata. 263 264 if body.startswith("#!"): 265 body = body[2:] 266 267 arguments, body = body.split("\n", 1) 268 269 # Get any parser/format declaration. 270 271 if arguments and not arguments[0].isspace(): 272 details = arguments.split(None, 1) 273 if len(details) == 2: 274 format, arguments = details 275 else: 276 format = details[0] 277 arguments = "" 278 else: 279 format = None 280 281 # Get the attributes/arguments for the region. 282 283 attributes = parseAttributes(arguments, False) 284 285 # Add an entry for the format in the attribute dictionary. 286 287 if format and not attributes.has_key(format): 288 attributes[format] = True 289 290 fragments.append((format, attributes, body)) 291 292 else: 293 fragments.append((None, {}, body)) 294 295 else: 296 fragments.append((None, {}, region)) 297 298 return fragments 299 300 def getFragments(s, include_non_regions=False): 301 302 """ 303 Return fragments for the given string 's', each having the form 304 (format, arguments, body text). 305 306 If 'include_non_regions' is specified as a true value, fragments will be 307 included for text between explicitly declared regions. 308 """ 309 310 return getFragmentsFromRegions(getRegions(s, include_non_regions)) 311 312 # Region/section attribute parsing. 313 314 def parseAttributes(s, escape=True): 315 316 """ 317 Parse the section attributes string 's', returning a mapping of names to 318 values. If 'escape' is set to a true value, the attributes will be suitable 319 for use with the formatter API. If 'escape' is set to a false value, the 320 attributes will have any quoting removed. 321 """ 322 323 attrs = {} 324 f = StringIO(s) 325 name = None 326 need_value = False 327 328 for token in shlex(f): 329 330 # Capture the name if needed. 331 332 if name is None: 333 name = escape and wikiutil.escape(token) or strip_token(token) 334 335 # Detect either an equals sign or another name. 336 337 elif not need_value: 338 if token == "=": 339 need_value = True 340 else: 341 attrs[name.lower()] = escape and "true" or True 342 name = wikiutil.escape(token) 343 344 # Otherwise, capture a value. 345 346 else: 347 # Quoting of attributes done similarly to wikiutil.parseAttributes. 348 349 if token: 350 if escape: 351 if token[0] in ("'", '"'): 352 token = wikiutil.escape(token) 353 else: 354 token = '"%s"' % wikiutil.escape(token, 1) 355 else: 356 token = strip_token(token) 357 358 attrs[name.lower()] = token 359 name = None 360 need_value = False 361 362 # Handle any name-only attributes at the end of the collection. 363 364 if name and not need_value: 365 attrs[name.lower()] = escape and "true" or True 366 367 return attrs 368 369 def strip_token(token): 370 371 "Return the given 'token' stripped of quoting." 372 373 if token[0] in ("'", '"') and token[-1] == token[0]: 374 return token[1:-1] 375 else: 376 return token 377 378 # Request-related classes and associated functions. 379 380 class Form: 381 382 """ 383 A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x 384 environment. 385 """ 386 387 def __init__(self, form): 388 self.form = form 389 390 def has_key(self, name): 391 return not not self.form.getlist(name) 392 393 def get(self, name, default=None): 394 values = self.form.getlist(name) 395 if not values: 396 return default 397 else: 398 return values 399 400 def __getitem__(self, name): 401 return self.form.getlist(name) 402 403 class ActionSupport: 404 405 """ 406 Work around disruptive MoinMoin changes in 1.9, and also provide useful 407 convenience methods. 408 """ 409 410 def get_form(self): 411 return get_form(self.request) 412 413 def _get_selected(self, value, input_value): 414 415 """ 416 Return the HTML attribute text indicating selection of an option (or 417 otherwise) if 'value' matches 'input_value'. 418 """ 419 420 return input_value is not None and value == input_value and 'selected="selected"' or '' 421 422 def _get_selected_for_list(self, value, input_values): 423 424 """ 425 Return the HTML attribute text indicating selection of an option (or 426 otherwise) if 'value' matches one of the 'input_values'. 427 """ 428 429 return value in input_values and 'selected="selected"' or '' 430 431 def get_option_list(self, value, values): 432 433 """ 434 Return a list of HTML element definitions for options describing the 435 given 'values', selecting the option with the specified 'value' if 436 present. 437 """ 438 439 options = [] 440 for available_value in values: 441 selected = self._get_selected(available_value, value) 442 options.append('<option value="%s" %s>%s</option>' % ( 443 escattr(available_value), selected, wikiutil.escape(available_value))) 444 return options 445 446 def _get_input(self, form, name, default=None): 447 448 """ 449 Return the input from 'form' having the given 'name', returning either 450 the input converted to an integer or the given 'default' (optional, None 451 if not specified). 452 """ 453 454 value = form.get(name, [None])[0] 455 if not value: # true if 0 obtained 456 return default 457 else: 458 return int(value) 459 460 def get_form(request): 461 462 "Work around disruptive MoinMoin changes in 1.9." 463 464 if hasattr(request, "values"): 465 return Form(request.values) 466 else: 467 return request.form 468 469 class send_headers_cls: 470 471 """ 472 A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a 473 1.9.x environment. 474 """ 475 476 def __init__(self, request): 477 self.request = request 478 479 def __call__(self, headers): 480 for header in headers: 481 parts = header.split(":") 482 self.request.headers.add(parts[0], ":".join(parts[1:])) 483 484 def get_send_headers(request): 485 486 "Return a function that can send response headers." 487 488 if hasattr(request, "http_headers"): 489 return request.http_headers 490 elif hasattr(request, "emit_http_headers"): 491 return request.emit_http_headers 492 else: 493 return send_headers_cls(request) 494 495 def escattr(s): 496 return wikiutil.escape(s, 1) 497 498 def getPathInfo(request): 499 if hasattr(request, "getPathinfo"): 500 return request.getPathinfo() 501 else: 502 return request.path 503 504 def getHeader(request, header_name, prefix=None): 505 506 """ 507 Using the 'request', return the value of the header with the given 508 'header_name', using the optional 'prefix' to obtain protocol-specific 509 headers if necessary. 510 511 If no value is found for the given 'header_name', None is returned. 512 """ 513 514 if hasattr(request, "getHeader"): 515 return request.getHeader(header_name) 516 elif hasattr(request, "headers"): 517 return request.headers.get(header_name) 518 else: 519 return request.env.get((prefix and prefix + "_" or "") + header_name.upper()) 520 521 def writeHeaders(request, mimetype, metadata, status=None): 522 523 """ 524 Using the 'request', write resource headers using the given 'mimetype', 525 based on the given 'metadata'. If the optional 'status' is specified, set 526 the status header to the given value. 527 """ 528 529 send_headers = get_send_headers(request) 530 531 # Define headers. 532 533 headers = ["Content-Type: %s; charset=%s" % (mimetype, config.charset)] 534 535 # Define the last modified time. 536 # NOTE: Consider using request.httpDate. 537 538 latest_timestamp = metadata.get("last-modified") 539 if latest_timestamp: 540 headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string()) 541 542 if status: 543 headers.append("Status: %s" % status) 544 545 send_headers(headers) 546 547 # Content/media type and preferences support. 548 549 class MediaRange: 550 551 "A content/media type value which supports whole categories of data." 552 553 def __init__(self, media_range, accept_parameters=None): 554 self.media_range = media_range 555 self.accept_parameters = accept_parameters or {} 556 557 parts = media_range.split(";") 558 self.media_type = parts[0] 559 self.parameters = getMappingFromParameterStrings(parts[1:]) 560 561 # The media type is divided into category and subcategory. 562 563 parts = self.media_type.split("/") 564 self.category = parts[0] 565 self.subcategory = "/".join(parts[1:]) 566 567 def get_parts(self): 568 569 "Return the category, subcategory parts." 570 571 return self.category, self.subcategory 572 573 def get_specificity(self): 574 575 """ 576 Return the specificity of the media type in terms of the scope of the 577 category and subcategory, and also in terms of any qualifying 578 parameters. 579 """ 580 581 if "*" in self.get_parts(): 582 return -list(self.get_parts()).count("*") 583 else: 584 return len(self.parameters) 585 586 def permits(self, other): 587 588 """ 589 Return whether this media type permits the use of the 'other' media type 590 if suggested as suitable content. 591 """ 592 593 if not isinstance(other, MediaRange): 594 other = MediaRange(other) 595 596 category = categoryPermits(self.category, other.category) 597 subcategory = categoryPermits(self.subcategory, other.subcategory) 598 599 if category and subcategory: 600 if "*" not in (category, subcategory): 601 return not self.parameters or self.parameters == other.parameters 602 else: 603 return True 604 else: 605 return False 606 607 def __eq__(self, other): 608 609 """ 610 Return whether this media type is effectively the same as the 'other' 611 media type. 612 """ 613 614 if not isinstance(other, MediaRange): 615 other = MediaRange(other) 616 617 category = categoryMatches(self.category, other.category) 618 subcategory = categoryMatches(self.subcategory, other.subcategory) 619 620 if category and subcategory: 621 if "*" not in (category, subcategory): 622 return self.parameters == other.parameters or \ 623 not self.parameters or not other.parameters 624 else: 625 return True 626 else: 627 return False 628 629 def __ne__(self, other): 630 return not self.__eq__(other) 631 632 def __hash__(self): 633 return hash(self.media_range) 634 635 def __repr__(self): 636 return "MediaRange(%r)" % self.media_range 637 638 def categoryMatches(this, that): 639 640 """ 641 Return the basis of a match between 'this' and 'that' or False if the given 642 categories do not match. 643 """ 644 645 return (this == "*" or this == that) and this or \ 646 that == "*" and that or False 647 648 def categoryPermits(this, that): 649 650 """ 651 Return whether 'this' category permits 'that' category. Where 'this' is a 652 wildcard ("*"), 'that' should always match. A value of False is returned if 653 the categories do not otherwise match. 654 """ 655 656 return (this == "*" or this == that) and this or False 657 658 def getMappingFromParameterStrings(l): 659 660 """ 661 Return a mapping representing the list of "name=value" strings given by 'l'. 662 """ 663 664 parameters = {} 665 666 for parameter in l: 667 parts = parameter.split("=") 668 name = parts[0].strip() 669 value = "=".join(parts[1:]).strip() 670 parameters[name] = value 671 672 return parameters 673 674 def getContentPreferences(accept): 675 676 """ 677 Return a mapping from media types to parameters for content/media types 678 extracted from the given 'accept' header value. The mapping is returned in 679 the form of a list of (media type, parameters) tuples. 680 681 See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 682 """ 683 684 preferences = [] 685 686 for field in accept.split(","): 687 688 # The media type with parameters (defined by the "media-range") is 689 # separated from any other parameters (defined as "accept-extension" 690 # parameters) by a quality parameter. 691 692 fparts = accept_regexp.split(field) 693 694 # The first part is always the media type. 695 696 media_type = fparts[0].strip() 697 698 # Any other parts can be interpreted as extension parameters. 699 700 if len(fparts) > 1: 701 fparts = ("q=" + ";q=".join(fparts[1:])).split(";") 702 else: 703 fparts = [] 704 705 # Each field in the preferences can incorporate parameters separated by 706 # semicolon characters. 707 708 parameters = getMappingFromParameterStrings(fparts) 709 media_range = MediaRange(media_type, parameters) 710 preferences.append(media_range) 711 712 return ContentPreferences(preferences) 713 714 class ContentPreferences: 715 716 "A wrapper around content preference information." 717 718 def __init__(self, preferences): 719 self.preferences = preferences 720 721 def __iter__(self): 722 return iter(self.preferences) 723 724 def get_ordered(self, by_quality=0): 725 726 """ 727 Return a list of content/media types in descending order of preference. 728 If 'by_quality' is set to a true value, the "q" value will be used as 729 the primary measure of preference; otherwise, only the specificity will 730 be considered. 731 """ 732 733 ordered = {} 734 735 for media_range in self.preferences: 736 specificity = media_range.get_specificity() 737 738 if by_quality: 739 q = float(media_range.accept_parameters.get("q", "1")) 740 key = q, specificity 741 else: 742 key = specificity 743 744 if not ordered.has_key(key): 745 ordered[key] = [] 746 747 ordered[key].append(media_range) 748 749 # Return the preferences in descending order of quality and specificity. 750 751 keys = ordered.keys() 752 keys.sort(reverse=True) 753 return [ordered[key] for key in keys] 754 755 def get_acceptable_types(self, available): 756 757 """ 758 Return content/media types from those in the 'available' list supported 759 by the known preferences grouped by preference level in descending order 760 of preference. 761 """ 762 763 matches = {} 764 available = set(available[:]) 765 766 for level in self.get_ordered(): 767 for media_range in level: 768 769 # Attempt to match available types. 770 771 found = set() 772 for available_type in available: 773 if media_range.permits(available_type): 774 q = float(media_range.accept_parameters.get("q", "1")) 775 if not matches.has_key(q): 776 matches[q] = [] 777 matches[q].append(available_type) 778 found.add(available_type) 779 780 # Stop looking for matches for matched available types. 781 782 if found: 783 available.difference_update(found) 784 785 # Sort the matches in descending order of quality. 786 787 all_q = matches.keys() 788 789 if all_q: 790 all_q.sort(reverse=True) 791 return [matches[q] for q in all_q] 792 else: 793 return [] 794 795 def get_preferred_types(self, available): 796 797 """ 798 Return the preferred content/media types from those in the 'available' 799 list, given the known preferences. 800 """ 801 802 preferred = self.get_acceptable_types(available) 803 if preferred: 804 return preferred[0] 805 else: 806 return [] 807 808 # Content type parsing. 809 810 def getContentTypeAndEncoding(content_type): 811 812 """ 813 Return a tuple with the content/media type and encoding, extracted from the 814 given 'content_type' header value. 815 """ 816 817 m = encoding_regexp.search(content_type) 818 if m: 819 return m.group("content_type"), m.group("encoding") 820 else: 821 return None, None 822 823 # Page access functions. 824 825 def getPageURL(page): 826 827 "Return the URL of the given 'page'." 828 829 request = page.request 830 return request.getQualifiedURL(page.url(request, relative=0)) 831 832 def getFormat(page): 833 834 "Get the format used on the given 'page'." 835 836 return page.pi["format"] 837 838 def getMetadata(page): 839 840 """ 841 Return a dictionary containing items describing for the given 'page' the 842 page's "created" time, "last-modified" time, "sequence" (or revision number) 843 and the "last-comment" made about the last edit. 844 """ 845 846 request = page.request 847 848 # Get the initial revision of the page. 849 850 revisions = page.getRevList() 851 852 if not revisions: 853 return {} 854 855 event_page_initial = Page(request, page.page_name, rev=revisions[-1]) 856 857 # Get the created and last modified times. 858 859 initial_revision = getPageRevision(event_page_initial) 860 861 metadata = {} 862 metadata["created"] = initial_revision["timestamp"] 863 latest_revision = getPageRevision(page) 864 metadata["last-modified"] = latest_revision["timestamp"] 865 metadata["sequence"] = len(revisions) - 1 866 metadata["last-comment"] = latest_revision["comment"] 867 868 return metadata 869 870 def getPageRevision(page): 871 872 "Return the revision details dictionary for the given 'page'." 873 874 # From Page.edit_info... 875 876 if hasattr(page, "editlog_entry"): 877 line = page.editlog_entry() 878 else: 879 line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x 880 881 # Similar to Page.mtime_usecs behaviour... 882 883 if line: 884 timestamp = line.ed_time_usecs 885 mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x 886 comment = line.comment 887 else: 888 mtime = 0 889 comment = "" 890 891 # Leave the time zone empty. 892 893 return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + (None,)), "comment" : comment} 894 895 # Page parsing and formatting of embedded content. 896 897 def getPageParserClass(request): 898 899 "Using 'request', return a parser class for the current page's format." 900 901 return getParserClass(request, getFormat(request.page)) 902 903 def getParserClass(request, format): 904 905 """ 906 Return a parser class using the 'request' for the given 'format', returning 907 a plain text parser if no parser can be found for the specified 'format'. 908 """ 909 910 try: 911 return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain") 912 except wikiutil.PluginMissingError: 913 return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain") 914 915 def getFormatterClass(request, format): 916 917 """ 918 Return a formatter class using the 'request' for the given output 'format', 919 returning a plain text formatter if no formatter can be found for the 920 specified 'format'. 921 """ 922 923 try: 924 return wikiutil.searchAndImportPlugin(request.cfg, "formatter", format or "plain") 925 except wikiutil.PluginMissingError: 926 return wikiutil.searchAndImportPlugin(request.cfg, "formatter", "plain") 927 928 def formatText(text, request, fmt, parser_cls=None): 929 930 """ 931 Format the given 'text' using the specified 'request' and formatter 'fmt'. 932 Suppress line anchors in the output, and fix lists by indicating that a 933 paragraph has already been started. 934 """ 935 936 if not parser_cls: 937 parser_cls = getPageParserClass(request) 938 parser = parser_cls(text, request, line_anchors=False) 939 940 old_fmt = request.formatter 941 request.formatter = fmt 942 try: 943 return redirectedOutput(request, parser, fmt, inhibit_p=True) 944 finally: 945 request.formatter = old_fmt 946 947 def redirectedOutput(request, parser, fmt, **kw): 948 949 "A fixed version of the request method of the same name." 950 951 buf = StringIO() 952 request.redirect(buf) 953 try: 954 parser.format(fmt, **kw) 955 if hasattr(fmt, "flush"): 956 buf.write(fmt.flush(True)) 957 finally: 958 request.redirect() 959 text = buf.getvalue() 960 buf.close() 961 return text 962 963 # Textual representations. 964 965 def getSimpleWikiText(text): 966 967 """ 968 Return the plain text representation of the given 'text' which may employ 969 certain Wiki syntax features, such as those providing verbatim or monospaced 970 text. 971 """ 972 973 # NOTE: Re-implementing support for verbatim text and linking avoidance. 974 975 return "".join([s for s in verbatim_regexp.split(text) if s is not None]) 976 977 def getEncodedWikiText(text): 978 979 "Encode the given 'text' in a verbatim representation." 980 981 return "<<Verbatim(%s)>>" % text 982 983 def getPrettyTitle(title): 984 985 "Return a nicely formatted version of the given 'title'." 986 987 return title.replace("_", " ").replace("/", u" ? ") 988 989 # User interface functions. 990 991 def getParameter(request, name, default=None): 992 993 """ 994 Using the given 'request', return the value of the parameter with the given 995 'name', returning the optional 'default' (or None) if no value was supplied 996 in the 'request'. 997 """ 998 999 return get_form(request).get(name, [default])[0] 1000 1001 def getQualifiedParameter(request, prefix, argname, default=None): 1002 1003 """ 1004 Using the given 'request', 'prefix' and 'argname', retrieve the value of the 1005 qualified parameter, returning the optional 'default' (or None) if no value 1006 was supplied in the 'request'. 1007 """ 1008 1009 argname = getQualifiedParameterName(prefix, argname) 1010 return getParameter(request, argname, default) 1011 1012 def getQualifiedParameterName(prefix, argname): 1013 1014 """ 1015 Return the qualified parameter name using the given 'prefix' and 'argname'. 1016 """ 1017 1018 if not prefix: 1019 return argname 1020 else: 1021 return "%s-%s" % (prefix, argname) 1022 1023 # Page-related functions. 1024 1025 def getPrettyPageName(page): 1026 1027 "Return a nicely formatted title/name for the given 'page'." 1028 1029 title = page.split_title(force=1) 1030 return getPrettyTitle(title) 1031 1032 def linkToPage(request, page, text, query_string=None, anchor=None, **kw): 1033 1034 """ 1035 Using 'request', return a link to 'page' with the given link 'text' and 1036 optional 'query_string' and 'anchor'. 1037 """ 1038 1039 text = wikiutil.escape(text) 1040 return page.link_to_raw(request, text, query_string, anchor, **kw) 1041 1042 def linkToResource(url, request, text, query_string=None, anchor=None): 1043 1044 """ 1045 Using 'request', return a link to 'url' with the given link 'text' and 1046 optional 'query_string' and 'anchor'. 1047 """ 1048 1049 if anchor: 1050 url += "#%s" % anchor 1051 1052 if query_string: 1053 query_string = wikiutil.makeQueryString(query_string) 1054 url += "?%s" % query_string 1055 1056 formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter 1057 1058 output = [] 1059 output.append(formatter.url(1, url)) 1060 output.append(formatter.text(text)) 1061 output.append(formatter.url(0)) 1062 return "".join(output) 1063 1064 def getFullPageName(parent, title): 1065 1066 """ 1067 Return a full page name from the given 'parent' page (can be empty or None) 1068 and 'title' (a simple page name). 1069 """ 1070 1071 if parent: 1072 return "%s/%s" % (parent.rstrip("/"), title) 1073 else: 1074 return title 1075 1076 # vim: tabstop=4 expandtab shiftwidth=4