paul@0 | 1 | # -*- coding: iso-8859-1 -*- |
paul@0 | 2 | """ |
paul@0 | 3 | MoinMoin - MoinSupport library (derived from EventAggregatorSupport) |
paul@0 | 4 | |
paul@0 | 5 | @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk> |
paul@0 | 6 | @copyright: 2000-2004 Juergen Hermann <jh@web.de>, |
paul@0 | 7 | 2005-2008 MoinMoin:ThomasWaldmann. |
paul@0 | 8 | @license: GNU GPL (v2 or later), see COPYING.txt for details. |
paul@0 | 9 | """ |
paul@0 | 10 | |
paul@0 | 11 | from DateSupport import * |
paul@1 | 12 | from MoinMoin.Page import Page |
paul@23 | 13 | from MoinMoin import config, wikiutil |
paul@10 | 14 | from StringIO import StringIO |
paul@10 | 15 | from shlex import shlex |
paul@0 | 16 | import re |
paul@0 | 17 | import time |
paul@0 | 18 | |
paul@10 | 19 | __version__ = "0.2" |
paul@0 | 20 | |
paul@0 | 21 | # Content type parsing. |
paul@0 | 22 | |
paul@0 | 23 | encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?' |
paul@0 | 24 | encoding_regexp = re.compile(encoding_regexp_str) |
paul@0 | 25 | |
paul@2 | 26 | # Accept header parsing. |
paul@2 | 27 | |
paul@2 | 28 | accept_regexp_str = ur';\s*q=' |
paul@2 | 29 | accept_regexp = re.compile(accept_regexp_str) |
paul@2 | 30 | |
paul@0 | 31 | # Utility functions. |
paul@0 | 32 | |
paul@0 | 33 | def getContentTypeAndEncoding(content_type): |
paul@2 | 34 | |
paul@2 | 35 | """ |
paul@2 | 36 | Return a tuple with the content/media type and encoding, extracted from the |
paul@2 | 37 | given 'content_type' header value. |
paul@2 | 38 | """ |
paul@2 | 39 | |
paul@0 | 40 | m = encoding_regexp.search(content_type) |
paul@0 | 41 | if m: |
paul@0 | 42 | return m.group("content_type"), m.group("encoding") |
paul@0 | 43 | else: |
paul@0 | 44 | return None, None |
paul@0 | 45 | |
paul@0 | 46 | def int_or_none(x): |
paul@0 | 47 | if x is None: |
paul@0 | 48 | return x |
paul@0 | 49 | else: |
paul@0 | 50 | return int(x) |
paul@0 | 51 | |
paul@10 | 52 | def parseAttributes(s, escape=True): |
paul@10 | 53 | |
paul@10 | 54 | """ |
paul@10 | 55 | Parse the section attributes string 's', returning a mapping of names to |
paul@10 | 56 | values. If 'escape' is set to a true value, the attributes will be suitable |
paul@10 | 57 | for use with the formatter API. If 'escape' is set to a false value, the |
paul@10 | 58 | attributes will have any quoting removed. |
paul@10 | 59 | """ |
paul@10 | 60 | |
paul@10 | 61 | attrs = {} |
paul@10 | 62 | f = StringIO(s) |
paul@10 | 63 | name = None |
paul@10 | 64 | need_value = False |
paul@10 | 65 | |
paul@10 | 66 | for token in shlex(f): |
paul@10 | 67 | |
paul@10 | 68 | # Capture the name if needed. |
paul@10 | 69 | |
paul@10 | 70 | if name is None: |
paul@10 | 71 | name = escape and wikiutil.escape(token) or strip_token(token) |
paul@10 | 72 | |
paul@10 | 73 | # Detect either an equals sign or another name. |
paul@10 | 74 | |
paul@10 | 75 | elif not need_value: |
paul@10 | 76 | if token == "=": |
paul@10 | 77 | need_value = True |
paul@10 | 78 | else: |
paul@10 | 79 | attrs[name.lower()] = escape and "true" or True |
paul@10 | 80 | name = wikiutil.escape(token) |
paul@10 | 81 | |
paul@10 | 82 | # Otherwise, capture a value. |
paul@10 | 83 | |
paul@10 | 84 | else: |
paul@10 | 85 | # Quoting of attributes done similarly to wikiutil.parseAttributes. |
paul@10 | 86 | |
paul@10 | 87 | if token: |
paul@10 | 88 | if escape: |
paul@10 | 89 | if token[0] in ("'", '"'): |
paul@10 | 90 | token = wikiutil.escape(token) |
paul@10 | 91 | else: |
paul@10 | 92 | token = '"%s"' % wikiutil.escape(token, 1) |
paul@10 | 93 | else: |
paul@10 | 94 | token = strip_token(token) |
paul@10 | 95 | |
paul@10 | 96 | attrs[name.lower()] = token |
paul@10 | 97 | name = None |
paul@10 | 98 | need_value = False |
paul@10 | 99 | |
paul@13 | 100 | # Handle any name-only attributes at the end of the collection. |
paul@13 | 101 | |
paul@13 | 102 | if name and not need_value: |
paul@13 | 103 | attrs[name.lower()] = escape and "true" or True |
paul@13 | 104 | |
paul@10 | 105 | return attrs |
paul@10 | 106 | |
paul@10 | 107 | def strip_token(token): |
paul@10 | 108 | |
paul@10 | 109 | "Return the given 'token' stripped of quoting." |
paul@10 | 110 | |
paul@10 | 111 | if token[0] in ("'", '"') and token[-1] == token[0]: |
paul@10 | 112 | return token[1:-1] |
paul@10 | 113 | else: |
paul@10 | 114 | return token |
paul@10 | 115 | |
paul@0 | 116 | # Utility classes and associated functions. |
paul@0 | 117 | |
paul@0 | 118 | class Form: |
paul@0 | 119 | |
paul@0 | 120 | """ |
paul@0 | 121 | A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x |
paul@0 | 122 | environment. |
paul@0 | 123 | """ |
paul@0 | 124 | |
paul@0 | 125 | def __init__(self, form): |
paul@0 | 126 | self.form = form |
paul@0 | 127 | |
paul@0 | 128 | def has_key(self, name): |
paul@0 | 129 | return not not self.form.getlist(name) |
paul@0 | 130 | |
paul@0 | 131 | def get(self, name, default=None): |
paul@0 | 132 | values = self.form.getlist(name) |
paul@0 | 133 | if not values: |
paul@0 | 134 | return default |
paul@0 | 135 | else: |
paul@0 | 136 | return values |
paul@0 | 137 | |
paul@0 | 138 | def __getitem__(self, name): |
paul@0 | 139 | return self.form.getlist(name) |
paul@0 | 140 | |
paul@0 | 141 | class ActionSupport: |
paul@0 | 142 | |
paul@0 | 143 | """ |
paul@0 | 144 | Work around disruptive MoinMoin changes in 1.9, and also provide useful |
paul@0 | 145 | convenience methods. |
paul@0 | 146 | """ |
paul@0 | 147 | |
paul@0 | 148 | def get_form(self): |
paul@0 | 149 | return get_form(self.request) |
paul@0 | 150 | |
paul@0 | 151 | def _get_selected(self, value, input_value): |
paul@0 | 152 | |
paul@0 | 153 | """ |
paul@0 | 154 | Return the HTML attribute text indicating selection of an option (or |
paul@0 | 155 | otherwise) if 'value' matches 'input_value'. |
paul@0 | 156 | """ |
paul@0 | 157 | |
paul@0 | 158 | return input_value is not None and value == input_value and 'selected="selected"' or '' |
paul@0 | 159 | |
paul@0 | 160 | def _get_selected_for_list(self, value, input_values): |
paul@0 | 161 | |
paul@0 | 162 | """ |
paul@0 | 163 | Return the HTML attribute text indicating selection of an option (or |
paul@0 | 164 | otherwise) if 'value' matches one of the 'input_values'. |
paul@0 | 165 | """ |
paul@0 | 166 | |
paul@0 | 167 | return value in input_values and 'selected="selected"' or '' |
paul@0 | 168 | |
paul@0 | 169 | def _get_input(self, form, name, default=None): |
paul@0 | 170 | |
paul@0 | 171 | """ |
paul@0 | 172 | Return the input from 'form' having the given 'name', returning either |
paul@0 | 173 | the input converted to an integer or the given 'default' (optional, None |
paul@0 | 174 | if not specified). |
paul@0 | 175 | """ |
paul@0 | 176 | |
paul@0 | 177 | value = form.get(name, [None])[0] |
paul@0 | 178 | if not value: # true if 0 obtained |
paul@0 | 179 | return default |
paul@0 | 180 | else: |
paul@0 | 181 | return int(value) |
paul@0 | 182 | |
paul@0 | 183 | def get_form(request): |
paul@0 | 184 | |
paul@0 | 185 | "Work around disruptive MoinMoin changes in 1.9." |
paul@0 | 186 | |
paul@0 | 187 | if hasattr(request, "values"): |
paul@0 | 188 | return Form(request.values) |
paul@0 | 189 | else: |
paul@0 | 190 | return request.form |
paul@0 | 191 | |
paul@0 | 192 | class send_headers_cls: |
paul@0 | 193 | |
paul@0 | 194 | """ |
paul@0 | 195 | A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a |
paul@0 | 196 | 1.9.x environment. |
paul@0 | 197 | """ |
paul@0 | 198 | |
paul@0 | 199 | def __init__(self, request): |
paul@0 | 200 | self.request = request |
paul@0 | 201 | |
paul@0 | 202 | def __call__(self, headers): |
paul@0 | 203 | for header in headers: |
paul@0 | 204 | parts = header.split(":") |
paul@0 | 205 | self.request.headers.add(parts[0], ":".join(parts[1:])) |
paul@0 | 206 | |
paul@0 | 207 | def get_send_headers(request): |
paul@0 | 208 | |
paul@0 | 209 | "Return a function that can send response headers." |
paul@0 | 210 | |
paul@0 | 211 | if hasattr(request, "http_headers"): |
paul@0 | 212 | return request.http_headers |
paul@0 | 213 | elif hasattr(request, "emit_http_headers"): |
paul@0 | 214 | return request.emit_http_headers |
paul@0 | 215 | else: |
paul@0 | 216 | return send_headers_cls(request) |
paul@0 | 217 | |
paul@0 | 218 | def escattr(s): |
paul@0 | 219 | return wikiutil.escape(s, 1) |
paul@0 | 220 | |
paul@0 | 221 | def getPathInfo(request): |
paul@0 | 222 | if hasattr(request, "getPathinfo"): |
paul@0 | 223 | return request.getPathinfo() |
paul@0 | 224 | else: |
paul@0 | 225 | return request.path |
paul@0 | 226 | |
paul@14 | 227 | def getHeader(request, header_name, prefix=None): |
paul@17 | 228 | |
paul@17 | 229 | """ |
paul@17 | 230 | Using the 'request', return the value of the header with the given |
paul@17 | 231 | 'header_name', using the optional 'prefix' to obtain protocol-specific |
paul@17 | 232 | headers if necessary. |
paul@17 | 233 | |
paul@17 | 234 | If no value is found for the given 'header_name', None is returned. |
paul@17 | 235 | """ |
paul@17 | 236 | |
paul@14 | 237 | if hasattr(request, "getHeader"): |
paul@14 | 238 | return request.getHeader(header_name) |
paul@14 | 239 | elif hasattr(request, "headers"): |
paul@17 | 240 | return request.headers.get(header_name) |
paul@14 | 241 | else: |
paul@17 | 242 | return request.env.get((prefix and prefix + "_" or "") + header_name.upper()) |
paul@14 | 243 | |
paul@23 | 244 | def writeHeaders(request, mimetype, metadata, status=None): |
paul@23 | 245 | |
paul@23 | 246 | """ |
paul@23 | 247 | Using the 'request', write resource headers using the given 'mimetype', |
paul@23 | 248 | based on the given 'metadata'. If the optional 'status' is specified, set |
paul@23 | 249 | the status header to the given value. |
paul@23 | 250 | """ |
paul@23 | 251 | |
paul@23 | 252 | send_headers = get_send_headers(request) |
paul@23 | 253 | |
paul@23 | 254 | # Define headers. |
paul@23 | 255 | |
paul@23 | 256 | headers = ["Content-Type: %s; charset=%s" % (mimetype, config.charset)] |
paul@23 | 257 | |
paul@23 | 258 | # Define the last modified time. |
paul@23 | 259 | # NOTE: Consider using request.httpDate. |
paul@23 | 260 | |
paul@23 | 261 | latest_timestamp = metadata.get("last-modified") |
paul@23 | 262 | if latest_timestamp: |
paul@23 | 263 | headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string()) |
paul@23 | 264 | |
paul@23 | 265 | if status: |
paul@23 | 266 | headers.append("Status: %s" % status) |
paul@23 | 267 | |
paul@23 | 268 | send_headers(headers) |
paul@23 | 269 | |
paul@2 | 270 | # Content/media type and preferences support. |
paul@2 | 271 | |
paul@2 | 272 | class MediaRange: |
paul@2 | 273 | |
paul@2 | 274 | "A content/media type value which supports whole categories of data." |
paul@2 | 275 | |
paul@2 | 276 | def __init__(self, media_range, accept_parameters=None): |
paul@2 | 277 | self.media_range = media_range |
paul@2 | 278 | self.accept_parameters = accept_parameters or {} |
paul@2 | 279 | |
paul@2 | 280 | parts = media_range.split(";") |
paul@2 | 281 | self.media_type = parts[0] |
paul@2 | 282 | self.parameters = getMappingFromParameterStrings(parts[1:]) |
paul@2 | 283 | |
paul@2 | 284 | # The media type is divided into category and subcategory. |
paul@2 | 285 | |
paul@2 | 286 | parts = self.media_type.split("/") |
paul@2 | 287 | self.category = parts[0] |
paul@2 | 288 | self.subcategory = "/".join(parts[1:]) |
paul@2 | 289 | |
paul@2 | 290 | def get_parts(self): |
paul@3 | 291 | |
paul@3 | 292 | "Return the category, subcategory parts." |
paul@3 | 293 | |
paul@2 | 294 | return self.category, self.subcategory |
paul@2 | 295 | |
paul@2 | 296 | def get_specificity(self): |
paul@3 | 297 | |
paul@3 | 298 | """ |
paul@3 | 299 | Return the specificity of the media type in terms of the scope of the |
paul@3 | 300 | category and subcategory, and also in terms of any qualifying |
paul@3 | 301 | parameters. |
paul@3 | 302 | """ |
paul@3 | 303 | |
paul@2 | 304 | if "*" in self.get_parts(): |
paul@2 | 305 | return -list(self.get_parts()).count("*") |
paul@2 | 306 | else: |
paul@2 | 307 | return len(self.parameters) |
paul@2 | 308 | |
paul@2 | 309 | def permits(self, other): |
paul@3 | 310 | |
paul@3 | 311 | """ |
paul@3 | 312 | Return whether this media type permits the use of the 'other' media type |
paul@3 | 313 | if suggested as suitable content. |
paul@3 | 314 | """ |
paul@3 | 315 | |
paul@2 | 316 | if not isinstance(other, MediaRange): |
paul@2 | 317 | other = MediaRange(other) |
paul@2 | 318 | |
paul@2 | 319 | category = categoryPermits(self.category, other.category) |
paul@2 | 320 | subcategory = categoryPermits(self.subcategory, other.subcategory) |
paul@2 | 321 | |
paul@2 | 322 | if category and subcategory: |
paul@2 | 323 | if "*" not in (category, subcategory): |
paul@2 | 324 | return not self.parameters or self.parameters == other.parameters |
paul@2 | 325 | else: |
paul@2 | 326 | return True |
paul@2 | 327 | else: |
paul@2 | 328 | return False |
paul@2 | 329 | |
paul@2 | 330 | def __eq__(self, other): |
paul@3 | 331 | |
paul@3 | 332 | """ |
paul@3 | 333 | Return whether this media type is effectively the same as the 'other' |
paul@3 | 334 | media type. |
paul@3 | 335 | """ |
paul@3 | 336 | |
paul@2 | 337 | if not isinstance(other, MediaRange): |
paul@2 | 338 | other = MediaRange(other) |
paul@2 | 339 | |
paul@2 | 340 | category = categoryMatches(self.category, other.category) |
paul@2 | 341 | subcategory = categoryMatches(self.subcategory, other.subcategory) |
paul@2 | 342 | |
paul@2 | 343 | if category and subcategory: |
paul@2 | 344 | if "*" not in (category, subcategory): |
paul@2 | 345 | return self.parameters == other.parameters or \ |
paul@2 | 346 | not self.parameters or not other.parameters |
paul@2 | 347 | else: |
paul@2 | 348 | return True |
paul@2 | 349 | else: |
paul@2 | 350 | return False |
paul@2 | 351 | |
paul@2 | 352 | def __ne__(self, other): |
paul@2 | 353 | return not self.__eq__(other) |
paul@2 | 354 | |
paul@2 | 355 | def __hash__(self): |
paul@2 | 356 | return hash(self.media_range) |
paul@2 | 357 | |
paul@2 | 358 | def __repr__(self): |
paul@2 | 359 | return "MediaRange(%r)" % self.media_range |
paul@2 | 360 | |
paul@2 | 361 | def categoryMatches(this, that): |
paul@2 | 362 | |
paul@2 | 363 | """ |
paul@2 | 364 | Return the basis of a match between 'this' and 'that' or False if the given |
paul@2 | 365 | categories do not match. |
paul@2 | 366 | """ |
paul@2 | 367 | |
paul@2 | 368 | return (this == "*" or this == that) and this or \ |
paul@2 | 369 | that == "*" and that or False |
paul@2 | 370 | |
paul@2 | 371 | def categoryPermits(this, that): |
paul@2 | 372 | |
paul@2 | 373 | """ |
paul@2 | 374 | Return whether 'this' category permits 'that' category. Where 'this' is a |
paul@2 | 375 | wildcard ("*"), 'that' should always match. A value of False is returned if |
paul@2 | 376 | the categories do not otherwise match. |
paul@2 | 377 | """ |
paul@2 | 378 | |
paul@2 | 379 | return (this == "*" or this == that) and this or False |
paul@2 | 380 | |
paul@2 | 381 | def getMappingFromParameterStrings(l): |
paul@2 | 382 | |
paul@2 | 383 | """ |
paul@2 | 384 | Return a mapping representing the list of "name=value" strings given by 'l'. |
paul@2 | 385 | """ |
paul@2 | 386 | |
paul@2 | 387 | parameters = {} |
paul@2 | 388 | |
paul@2 | 389 | for parameter in l: |
paul@2 | 390 | parts = parameter.split("=") |
paul@2 | 391 | name = parts[0].strip() |
paul@2 | 392 | value = "=".join(parts[1:]).strip() |
paul@2 | 393 | parameters[name] = value |
paul@2 | 394 | |
paul@2 | 395 | return parameters |
paul@2 | 396 | |
paul@2 | 397 | def getContentPreferences(accept): |
paul@2 | 398 | |
paul@2 | 399 | """ |
paul@2 | 400 | Return a mapping from media types to parameters for content/media types |
paul@2 | 401 | extracted from the given 'accept' header value. The mapping is returned in |
paul@2 | 402 | the form of a list of (media type, parameters) tuples. |
paul@2 | 403 | |
paul@2 | 404 | See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 |
paul@2 | 405 | """ |
paul@2 | 406 | |
paul@2 | 407 | preferences = [] |
paul@2 | 408 | |
paul@2 | 409 | for field in accept.split(","): |
paul@2 | 410 | |
paul@2 | 411 | # The media type with parameters (defined by the "media-range") is |
paul@2 | 412 | # separated from any other parameters (defined as "accept-extension" |
paul@2 | 413 | # parameters) by a quality parameter. |
paul@2 | 414 | |
paul@2 | 415 | fparts = accept_regexp.split(field) |
paul@2 | 416 | |
paul@2 | 417 | # The first part is always the media type. |
paul@2 | 418 | |
paul@2 | 419 | media_type = fparts[0].strip() |
paul@2 | 420 | |
paul@2 | 421 | # Any other parts can be interpreted as extension parameters. |
paul@2 | 422 | |
paul@2 | 423 | if len(fparts) > 1: |
paul@2 | 424 | fparts = ("q=" + ";q=".join(fparts[1:])).split(";") |
paul@2 | 425 | else: |
paul@2 | 426 | fparts = [] |
paul@2 | 427 | |
paul@2 | 428 | # Each field in the preferences can incorporate parameters separated by |
paul@2 | 429 | # semicolon characters. |
paul@2 | 430 | |
paul@2 | 431 | parameters = getMappingFromParameterStrings(fparts) |
paul@2 | 432 | media_range = MediaRange(media_type, parameters) |
paul@2 | 433 | preferences.append(media_range) |
paul@2 | 434 | |
paul@2 | 435 | return ContentPreferences(preferences) |
paul@2 | 436 | |
paul@2 | 437 | class ContentPreferences: |
paul@2 | 438 | |
paul@2 | 439 | "A wrapper around content preference information." |
paul@2 | 440 | |
paul@2 | 441 | def __init__(self, preferences): |
paul@2 | 442 | self.preferences = preferences |
paul@2 | 443 | |
paul@2 | 444 | def __iter__(self): |
paul@2 | 445 | return iter(self.preferences) |
paul@2 | 446 | |
paul@2 | 447 | def get_ordered(self, by_quality=0): |
paul@2 | 448 | |
paul@2 | 449 | """ |
paul@2 | 450 | Return a list of content/media types in descending order of preference. |
paul@2 | 451 | If 'by_quality' is set to a true value, the "q" value will be used as |
paul@2 | 452 | the primary measure of preference; otherwise, only the specificity will |
paul@2 | 453 | be considered. |
paul@2 | 454 | """ |
paul@2 | 455 | |
paul@2 | 456 | ordered = {} |
paul@2 | 457 | |
paul@2 | 458 | for media_range in self.preferences: |
paul@2 | 459 | specificity = media_range.get_specificity() |
paul@2 | 460 | |
paul@2 | 461 | if by_quality: |
paul@2 | 462 | q = float(media_range.accept_parameters.get("q", "1")) |
paul@2 | 463 | key = q, specificity |
paul@2 | 464 | else: |
paul@2 | 465 | key = specificity |
paul@2 | 466 | |
paul@2 | 467 | if not ordered.has_key(key): |
paul@2 | 468 | ordered[key] = [] |
paul@2 | 469 | |
paul@2 | 470 | ordered[key].append(media_range) |
paul@2 | 471 | |
paul@2 | 472 | # Return the preferences in descending order of quality and specificity. |
paul@2 | 473 | |
paul@2 | 474 | keys = ordered.keys() |
paul@2 | 475 | keys.sort(reverse=True) |
paul@2 | 476 | return [ordered[key] for key in keys] |
paul@2 | 477 | |
paul@14 | 478 | def get_acceptable_types(self, available): |
paul@2 | 479 | |
paul@2 | 480 | """ |
paul@14 | 481 | Return content/media types from those in the 'available' list supported |
paul@14 | 482 | by the known preferences grouped by preference level in descending order |
paul@14 | 483 | of preference. |
paul@2 | 484 | """ |
paul@2 | 485 | |
paul@2 | 486 | matches = {} |
paul@2 | 487 | available = set(available[:]) |
paul@2 | 488 | |
paul@2 | 489 | for level in self.get_ordered(): |
paul@2 | 490 | for media_range in level: |
paul@2 | 491 | |
paul@2 | 492 | # Attempt to match available types. |
paul@2 | 493 | |
paul@2 | 494 | found = set() |
paul@2 | 495 | for available_type in available: |
paul@2 | 496 | if media_range.permits(available_type): |
paul@2 | 497 | q = float(media_range.accept_parameters.get("q", "1")) |
paul@2 | 498 | if not matches.has_key(q): |
paul@2 | 499 | matches[q] = [] |
paul@2 | 500 | matches[q].append(available_type) |
paul@2 | 501 | found.add(available_type) |
paul@2 | 502 | |
paul@2 | 503 | # Stop looking for matches for matched available types. |
paul@2 | 504 | |
paul@2 | 505 | if found: |
paul@2 | 506 | available.difference_update(found) |
paul@2 | 507 | |
paul@2 | 508 | # Sort the matches in descending order of quality. |
paul@2 | 509 | |
paul@2 | 510 | all_q = matches.keys() |
paul@2 | 511 | |
paul@2 | 512 | if all_q: |
paul@2 | 513 | all_q.sort(reverse=True) |
paul@14 | 514 | return [matches[q] for q in all_q] |
paul@2 | 515 | else: |
paul@14 | 516 | return [] |
paul@14 | 517 | |
paul@14 | 518 | def get_preferred_types(self, available): |
paul@14 | 519 | |
paul@14 | 520 | """ |
paul@14 | 521 | Return the preferred content/media types from those in the 'available' |
paul@14 | 522 | list, given the known preferences. |
paul@14 | 523 | """ |
paul@14 | 524 | |
paul@14 | 525 | preferred = self.get_acceptable_types(available) |
paul@14 | 526 | if preferred: |
paul@14 | 527 | return preferred[0] |
paul@14 | 528 | else: |
paul@14 | 529 | return [] |
paul@2 | 530 | |
paul@1 | 531 | # Page access functions. |
paul@1 | 532 | |
paul@1 | 533 | def getPageURL(page): |
paul@1 | 534 | |
paul@1 | 535 | "Return the URL of the given 'page'." |
paul@1 | 536 | |
paul@1 | 537 | request = page.request |
paul@1 | 538 | return request.getQualifiedURL(page.url(request, relative=0)) |
paul@1 | 539 | |
paul@1 | 540 | def getFormat(page): |
paul@1 | 541 | |
paul@1 | 542 | "Get the format used on the given 'page'." |
paul@1 | 543 | |
paul@1 | 544 | return page.pi["format"] |
paul@1 | 545 | |
paul@1 | 546 | def getMetadata(page): |
paul@1 | 547 | |
paul@1 | 548 | """ |
paul@1 | 549 | Return a dictionary containing items describing for the given 'page' the |
paul@1 | 550 | page's "created" time, "last-modified" time, "sequence" (or revision number) |
paul@1 | 551 | and the "last-comment" made about the last edit. |
paul@1 | 552 | """ |
paul@1 | 553 | |
paul@1 | 554 | request = page.request |
paul@1 | 555 | |
paul@1 | 556 | # Get the initial revision of the page. |
paul@1 | 557 | |
paul@1 | 558 | revisions = page.getRevList() |
paul@1 | 559 | event_page_initial = Page(request, page.page_name, rev=revisions[-1]) |
paul@1 | 560 | |
paul@1 | 561 | # Get the created and last modified times. |
paul@1 | 562 | |
paul@1 | 563 | initial_revision = getPageRevision(event_page_initial) |
paul@1 | 564 | |
paul@1 | 565 | metadata = {} |
paul@1 | 566 | metadata["created"] = initial_revision["timestamp"] |
paul@1 | 567 | latest_revision = getPageRevision(page) |
paul@1 | 568 | metadata["last-modified"] = latest_revision["timestamp"] |
paul@1 | 569 | metadata["sequence"] = len(revisions) - 1 |
paul@1 | 570 | metadata["last-comment"] = latest_revision["comment"] |
paul@1 | 571 | |
paul@1 | 572 | return metadata |
paul@0 | 573 | |
paul@0 | 574 | def getPageRevision(page): |
paul@0 | 575 | |
paul@0 | 576 | "Return the revision details dictionary for the given 'page'." |
paul@0 | 577 | |
paul@0 | 578 | # From Page.edit_info... |
paul@0 | 579 | |
paul@0 | 580 | if hasattr(page, "editlog_entry"): |
paul@0 | 581 | line = page.editlog_entry() |
paul@0 | 582 | else: |
paul@0 | 583 | line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x |
paul@0 | 584 | |
paul@0 | 585 | # Similar to Page.mtime_usecs behaviour... |
paul@0 | 586 | |
paul@0 | 587 | if line: |
paul@0 | 588 | timestamp = line.ed_time_usecs |
paul@0 | 589 | mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x |
paul@0 | 590 | comment = line.comment |
paul@0 | 591 | else: |
paul@0 | 592 | mtime = 0 |
paul@0 | 593 | comment = "" |
paul@0 | 594 | |
paul@0 | 595 | # Leave the time zone empty. |
paul@0 | 596 | |
paul@0 | 597 | return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + (None,)), "comment" : comment} |
paul@0 | 598 | |
paul@11 | 599 | # Page parsing and formatting of embedded content. |
paul@11 | 600 | |
paul@15 | 601 | def getPageParserClass(request): |
paul@15 | 602 | |
paul@15 | 603 | "Using 'request', return a parser class for the current page's format." |
paul@15 | 604 | |
paul@16 | 605 | return getParserClass(request, getFormat(request.page)) |
paul@15 | 606 | |
paul@11 | 607 | def getParserClass(request, format): |
paul@11 | 608 | |
paul@11 | 609 | """ |
paul@11 | 610 | Return a parser class using the 'request' for the given 'format', returning |
paul@11 | 611 | a plain text parser if no parser can be found for the specified 'format'. |
paul@11 | 612 | """ |
paul@11 | 613 | |
paul@11 | 614 | try: |
paul@11 | 615 | return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain") |
paul@11 | 616 | except wikiutil.PluginMissingError: |
paul@11 | 617 | return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain") |
paul@11 | 618 | |
paul@15 | 619 | def getFormatterClass(request, format): |
paul@15 | 620 | |
paul@15 | 621 | """ |
paul@15 | 622 | Return a formatter class using the 'request' for the given output 'format', |
paul@15 | 623 | returning a plain text formatter if no formatter can be found for the |
paul@15 | 624 | specified 'format'. |
paul@15 | 625 | """ |
paul@15 | 626 | |
paul@15 | 627 | try: |
paul@15 | 628 | return wikiutil.searchAndImportPlugin(request.cfg, "formatter", format or "plain") |
paul@15 | 629 | except wikiutil.PluginMissingError: |
paul@15 | 630 | return wikiutil.searchAndImportPlugin(request.cfg, "formatter", "plain") |
paul@15 | 631 | |
paul@15 | 632 | def formatText(text, request, fmt, parser_cls=None): |
paul@15 | 633 | |
paul@15 | 634 | """ |
paul@15 | 635 | Format the given 'text' using the specified 'request' and formatter 'fmt'. |
paul@15 | 636 | Suppress line anchors in the output, and fix lists by indicating that a |
paul@15 | 637 | paragraph has already been started. |
paul@15 | 638 | """ |
paul@15 | 639 | |
paul@15 | 640 | if not parser_cls: |
paul@15 | 641 | parser_cls = getPageParserClass(request) |
paul@15 | 642 | parser = parser_cls(text, request, line_anchors=False) |
paul@15 | 643 | |
paul@15 | 644 | old_fmt = request.formatter |
paul@15 | 645 | request.formatter = fmt |
paul@15 | 646 | try: |
paul@15 | 647 | return redirectedOutput(request, parser, fmt, inhibit_p=True) |
paul@15 | 648 | finally: |
paul@15 | 649 | request.formatter = old_fmt |
paul@15 | 650 | |
paul@11 | 651 | def redirectedOutput(request, parser, fmt, **kw): |
paul@11 | 652 | |
paul@11 | 653 | "A fixed version of the request method of the same name." |
paul@11 | 654 | |
paul@11 | 655 | buf = StringIO() |
paul@11 | 656 | request.redirect(buf) |
paul@11 | 657 | try: |
paul@11 | 658 | parser.format(fmt, **kw) |
paul@11 | 659 | if hasattr(fmt, "flush"): |
paul@11 | 660 | buf.write(fmt.flush(True)) |
paul@11 | 661 | finally: |
paul@11 | 662 | request.redirect() |
paul@11 | 663 | text = buf.getvalue() |
paul@11 | 664 | buf.close() |
paul@11 | 665 | return text |
paul@11 | 666 | |
paul@0 | 667 | # User interface functions. |
paul@0 | 668 | |
paul@0 | 669 | def getParameter(request, name, default=None): |
paul@0 | 670 | |
paul@0 | 671 | """ |
paul@0 | 672 | Using the given 'request', return the value of the parameter with the given |
paul@0 | 673 | 'name', returning the optional 'default' (or None) if no value was supplied |
paul@0 | 674 | in the 'request'. |
paul@0 | 675 | """ |
paul@0 | 676 | |
paul@0 | 677 | return get_form(request).get(name, [default])[0] |
paul@0 | 678 | |
paul@0 | 679 | def getQualifiedParameter(request, prefix, argname, default=None): |
paul@0 | 680 | |
paul@0 | 681 | """ |
paul@0 | 682 | Using the given 'request', 'prefix' and 'argname', retrieve the value of the |
paul@0 | 683 | qualified parameter, returning the optional 'default' (or None) if no value |
paul@0 | 684 | was supplied in the 'request'. |
paul@0 | 685 | """ |
paul@0 | 686 | |
paul@0 | 687 | argname = getQualifiedParameterName(prefix, argname) |
paul@0 | 688 | return getParameter(request, argname, default) |
paul@0 | 689 | |
paul@0 | 690 | def getQualifiedParameterName(prefix, argname): |
paul@0 | 691 | |
paul@0 | 692 | """ |
paul@0 | 693 | Return the qualified parameter name using the given 'prefix' and 'argname'. |
paul@0 | 694 | """ |
paul@0 | 695 | |
paul@0 | 696 | if prefix is None: |
paul@0 | 697 | return argname |
paul@0 | 698 | else: |
paul@0 | 699 | return "%s-%s" % (prefix, argname) |
paul@0 | 700 | |
paul@0 | 701 | # Page-related functions. |
paul@0 | 702 | |
paul@0 | 703 | def getPrettyPageName(page): |
paul@0 | 704 | |
paul@0 | 705 | "Return a nicely formatted title/name for the given 'page'." |
paul@0 | 706 | |
paul@0 | 707 | title = page.split_title(force=1) |
paul@0 | 708 | return getPrettyTitle(title) |
paul@0 | 709 | |
paul@3 | 710 | def linkToPage(request, page, text, query_string=None, **kw): |
paul@0 | 711 | |
paul@0 | 712 | """ |
paul@0 | 713 | Using 'request', return a link to 'page' with the given link 'text' and |
paul@0 | 714 | optional 'query_string'. |
paul@0 | 715 | """ |
paul@0 | 716 | |
paul@0 | 717 | text = wikiutil.escape(text) |
paul@3 | 718 | return page.link_to_raw(request, text, query_string, **kw) |
paul@0 | 719 | |
paul@0 | 720 | def linkToResource(url, request, text, query_string=None): |
paul@0 | 721 | |
paul@0 | 722 | """ |
paul@0 | 723 | Using 'request', return a link to 'url' with the given link 'text' and |
paul@0 | 724 | optional 'query_string'. |
paul@0 | 725 | """ |
paul@0 | 726 | |
paul@0 | 727 | if query_string: |
paul@0 | 728 | query_string = wikiutil.makeQueryString(query_string) |
paul@0 | 729 | url = "%s?%s" % (url, query_string) |
paul@0 | 730 | |
paul@0 | 731 | formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter |
paul@0 | 732 | |
paul@0 | 733 | output = [] |
paul@0 | 734 | output.append(formatter.url(1, url)) |
paul@0 | 735 | output.append(formatter.text(text)) |
paul@0 | 736 | output.append(formatter.url(0)) |
paul@0 | 737 | return "".join(output) |
paul@0 | 738 | |
paul@0 | 739 | def getFullPageName(parent, title): |
paul@0 | 740 | |
paul@0 | 741 | """ |
paul@0 | 742 | Return a full page name from the given 'parent' page (can be empty or None) |
paul@0 | 743 | and 'title' (a simple page name). |
paul@0 | 744 | """ |
paul@0 | 745 | |
paul@0 | 746 | if parent: |
paul@0 | 747 | return "%s/%s" % (parent.rstrip("/"), title) |
paul@0 | 748 | else: |
paul@0 | 749 | return title |
paul@0 | 750 | |
paul@0 | 751 | # vim: tabstop=4 expandtab shiftwidth=4 |