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@0 | 13 | from MoinMoin import 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@10 | 100 | return attrs |
paul@10 | 101 | |
paul@10 | 102 | def strip_token(token): |
paul@10 | 103 | |
paul@10 | 104 | "Return the given 'token' stripped of quoting." |
paul@10 | 105 | |
paul@10 | 106 | if token[0] in ("'", '"') and token[-1] == token[0]: |
paul@10 | 107 | return token[1:-1] |
paul@10 | 108 | else: |
paul@10 | 109 | return token |
paul@10 | 110 | |
paul@0 | 111 | # Utility classes and associated functions. |
paul@0 | 112 | |
paul@0 | 113 | class Form: |
paul@0 | 114 | |
paul@0 | 115 | """ |
paul@0 | 116 | A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x |
paul@0 | 117 | environment. |
paul@0 | 118 | """ |
paul@0 | 119 | |
paul@0 | 120 | def __init__(self, form): |
paul@0 | 121 | self.form = form |
paul@0 | 122 | |
paul@0 | 123 | def has_key(self, name): |
paul@0 | 124 | return not not self.form.getlist(name) |
paul@0 | 125 | |
paul@0 | 126 | def get(self, name, default=None): |
paul@0 | 127 | values = self.form.getlist(name) |
paul@0 | 128 | if not values: |
paul@0 | 129 | return default |
paul@0 | 130 | else: |
paul@0 | 131 | return values |
paul@0 | 132 | |
paul@0 | 133 | def __getitem__(self, name): |
paul@0 | 134 | return self.form.getlist(name) |
paul@0 | 135 | |
paul@0 | 136 | class ActionSupport: |
paul@0 | 137 | |
paul@0 | 138 | """ |
paul@0 | 139 | Work around disruptive MoinMoin changes in 1.9, and also provide useful |
paul@0 | 140 | convenience methods. |
paul@0 | 141 | """ |
paul@0 | 142 | |
paul@0 | 143 | def get_form(self): |
paul@0 | 144 | return get_form(self.request) |
paul@0 | 145 | |
paul@0 | 146 | def _get_selected(self, value, input_value): |
paul@0 | 147 | |
paul@0 | 148 | """ |
paul@0 | 149 | Return the HTML attribute text indicating selection of an option (or |
paul@0 | 150 | otherwise) if 'value' matches 'input_value'. |
paul@0 | 151 | """ |
paul@0 | 152 | |
paul@0 | 153 | return input_value is not None and value == input_value and 'selected="selected"' or '' |
paul@0 | 154 | |
paul@0 | 155 | def _get_selected_for_list(self, value, input_values): |
paul@0 | 156 | |
paul@0 | 157 | """ |
paul@0 | 158 | Return the HTML attribute text indicating selection of an option (or |
paul@0 | 159 | otherwise) if 'value' matches one of the 'input_values'. |
paul@0 | 160 | """ |
paul@0 | 161 | |
paul@0 | 162 | return value in input_values and 'selected="selected"' or '' |
paul@0 | 163 | |
paul@0 | 164 | def _get_input(self, form, name, default=None): |
paul@0 | 165 | |
paul@0 | 166 | """ |
paul@0 | 167 | Return the input from 'form' having the given 'name', returning either |
paul@0 | 168 | the input converted to an integer or the given 'default' (optional, None |
paul@0 | 169 | if not specified). |
paul@0 | 170 | """ |
paul@0 | 171 | |
paul@0 | 172 | value = form.get(name, [None])[0] |
paul@0 | 173 | if not value: # true if 0 obtained |
paul@0 | 174 | return default |
paul@0 | 175 | else: |
paul@0 | 176 | return int(value) |
paul@0 | 177 | |
paul@0 | 178 | def get_form(request): |
paul@0 | 179 | |
paul@0 | 180 | "Work around disruptive MoinMoin changes in 1.9." |
paul@0 | 181 | |
paul@0 | 182 | if hasattr(request, "values"): |
paul@0 | 183 | return Form(request.values) |
paul@0 | 184 | else: |
paul@0 | 185 | return request.form |
paul@0 | 186 | |
paul@0 | 187 | class send_headers_cls: |
paul@0 | 188 | |
paul@0 | 189 | """ |
paul@0 | 190 | A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a |
paul@0 | 191 | 1.9.x environment. |
paul@0 | 192 | """ |
paul@0 | 193 | |
paul@0 | 194 | def __init__(self, request): |
paul@0 | 195 | self.request = request |
paul@0 | 196 | |
paul@0 | 197 | def __call__(self, headers): |
paul@0 | 198 | for header in headers: |
paul@0 | 199 | parts = header.split(":") |
paul@0 | 200 | self.request.headers.add(parts[0], ":".join(parts[1:])) |
paul@0 | 201 | |
paul@0 | 202 | def get_send_headers(request): |
paul@0 | 203 | |
paul@0 | 204 | "Return a function that can send response headers." |
paul@0 | 205 | |
paul@0 | 206 | if hasattr(request, "http_headers"): |
paul@0 | 207 | return request.http_headers |
paul@0 | 208 | elif hasattr(request, "emit_http_headers"): |
paul@0 | 209 | return request.emit_http_headers |
paul@0 | 210 | else: |
paul@0 | 211 | return send_headers_cls(request) |
paul@0 | 212 | |
paul@0 | 213 | def escattr(s): |
paul@0 | 214 | return wikiutil.escape(s, 1) |
paul@0 | 215 | |
paul@0 | 216 | def getPathInfo(request): |
paul@0 | 217 | if hasattr(request, "getPathinfo"): |
paul@0 | 218 | return request.getPathinfo() |
paul@0 | 219 | else: |
paul@0 | 220 | return request.path |
paul@0 | 221 | |
paul@2 | 222 | # Content/media type and preferences support. |
paul@2 | 223 | |
paul@2 | 224 | class MediaRange: |
paul@2 | 225 | |
paul@2 | 226 | "A content/media type value which supports whole categories of data." |
paul@2 | 227 | |
paul@2 | 228 | def __init__(self, media_range, accept_parameters=None): |
paul@2 | 229 | self.media_range = media_range |
paul@2 | 230 | self.accept_parameters = accept_parameters or {} |
paul@2 | 231 | |
paul@2 | 232 | parts = media_range.split(";") |
paul@2 | 233 | self.media_type = parts[0] |
paul@2 | 234 | self.parameters = getMappingFromParameterStrings(parts[1:]) |
paul@2 | 235 | |
paul@2 | 236 | # The media type is divided into category and subcategory. |
paul@2 | 237 | |
paul@2 | 238 | parts = self.media_type.split("/") |
paul@2 | 239 | self.category = parts[0] |
paul@2 | 240 | self.subcategory = "/".join(parts[1:]) |
paul@2 | 241 | |
paul@2 | 242 | def get_parts(self): |
paul@3 | 243 | |
paul@3 | 244 | "Return the category, subcategory parts." |
paul@3 | 245 | |
paul@2 | 246 | return self.category, self.subcategory |
paul@2 | 247 | |
paul@2 | 248 | def get_specificity(self): |
paul@3 | 249 | |
paul@3 | 250 | """ |
paul@3 | 251 | Return the specificity of the media type in terms of the scope of the |
paul@3 | 252 | category and subcategory, and also in terms of any qualifying |
paul@3 | 253 | parameters. |
paul@3 | 254 | """ |
paul@3 | 255 | |
paul@2 | 256 | if "*" in self.get_parts(): |
paul@2 | 257 | return -list(self.get_parts()).count("*") |
paul@2 | 258 | else: |
paul@2 | 259 | return len(self.parameters) |
paul@2 | 260 | |
paul@2 | 261 | def permits(self, other): |
paul@3 | 262 | |
paul@3 | 263 | """ |
paul@3 | 264 | Return whether this media type permits the use of the 'other' media type |
paul@3 | 265 | if suggested as suitable content. |
paul@3 | 266 | """ |
paul@3 | 267 | |
paul@2 | 268 | if not isinstance(other, MediaRange): |
paul@2 | 269 | other = MediaRange(other) |
paul@2 | 270 | |
paul@2 | 271 | category = categoryPermits(self.category, other.category) |
paul@2 | 272 | subcategory = categoryPermits(self.subcategory, other.subcategory) |
paul@2 | 273 | |
paul@2 | 274 | if category and subcategory: |
paul@2 | 275 | if "*" not in (category, subcategory): |
paul@2 | 276 | return not self.parameters or self.parameters == other.parameters |
paul@2 | 277 | else: |
paul@2 | 278 | return True |
paul@2 | 279 | else: |
paul@2 | 280 | return False |
paul@2 | 281 | |
paul@2 | 282 | def __eq__(self, other): |
paul@3 | 283 | |
paul@3 | 284 | """ |
paul@3 | 285 | Return whether this media type is effectively the same as the 'other' |
paul@3 | 286 | media type. |
paul@3 | 287 | """ |
paul@3 | 288 | |
paul@2 | 289 | if not isinstance(other, MediaRange): |
paul@2 | 290 | other = MediaRange(other) |
paul@2 | 291 | |
paul@2 | 292 | category = categoryMatches(self.category, other.category) |
paul@2 | 293 | subcategory = categoryMatches(self.subcategory, other.subcategory) |
paul@2 | 294 | |
paul@2 | 295 | if category and subcategory: |
paul@2 | 296 | if "*" not in (category, subcategory): |
paul@2 | 297 | return self.parameters == other.parameters or \ |
paul@2 | 298 | not self.parameters or not other.parameters |
paul@2 | 299 | else: |
paul@2 | 300 | return True |
paul@2 | 301 | else: |
paul@2 | 302 | return False |
paul@2 | 303 | |
paul@2 | 304 | def __ne__(self, other): |
paul@2 | 305 | return not self.__eq__(other) |
paul@2 | 306 | |
paul@2 | 307 | def __hash__(self): |
paul@2 | 308 | return hash(self.media_range) |
paul@2 | 309 | |
paul@2 | 310 | def __repr__(self): |
paul@2 | 311 | return "MediaRange(%r)" % self.media_range |
paul@2 | 312 | |
paul@2 | 313 | def categoryMatches(this, that): |
paul@2 | 314 | |
paul@2 | 315 | """ |
paul@2 | 316 | Return the basis of a match between 'this' and 'that' or False if the given |
paul@2 | 317 | categories do not match. |
paul@2 | 318 | """ |
paul@2 | 319 | |
paul@2 | 320 | return (this == "*" or this == that) and this or \ |
paul@2 | 321 | that == "*" and that or False |
paul@2 | 322 | |
paul@2 | 323 | def categoryPermits(this, that): |
paul@2 | 324 | |
paul@2 | 325 | """ |
paul@2 | 326 | Return whether 'this' category permits 'that' category. Where 'this' is a |
paul@2 | 327 | wildcard ("*"), 'that' should always match. A value of False is returned if |
paul@2 | 328 | the categories do not otherwise match. |
paul@2 | 329 | """ |
paul@2 | 330 | |
paul@2 | 331 | return (this == "*" or this == that) and this or False |
paul@2 | 332 | |
paul@2 | 333 | def getMappingFromParameterStrings(l): |
paul@2 | 334 | |
paul@2 | 335 | """ |
paul@2 | 336 | Return a mapping representing the list of "name=value" strings given by 'l'. |
paul@2 | 337 | """ |
paul@2 | 338 | |
paul@2 | 339 | parameters = {} |
paul@2 | 340 | |
paul@2 | 341 | for parameter in l: |
paul@2 | 342 | parts = parameter.split("=") |
paul@2 | 343 | name = parts[0].strip() |
paul@2 | 344 | value = "=".join(parts[1:]).strip() |
paul@2 | 345 | parameters[name] = value |
paul@2 | 346 | |
paul@2 | 347 | return parameters |
paul@2 | 348 | |
paul@2 | 349 | def getContentPreferences(accept): |
paul@2 | 350 | |
paul@2 | 351 | """ |
paul@2 | 352 | Return a mapping from media types to parameters for content/media types |
paul@2 | 353 | extracted from the given 'accept' header value. The mapping is returned in |
paul@2 | 354 | the form of a list of (media type, parameters) tuples. |
paul@2 | 355 | |
paul@2 | 356 | See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 |
paul@2 | 357 | """ |
paul@2 | 358 | |
paul@2 | 359 | preferences = [] |
paul@2 | 360 | |
paul@2 | 361 | for field in accept.split(","): |
paul@2 | 362 | |
paul@2 | 363 | # The media type with parameters (defined by the "media-range") is |
paul@2 | 364 | # separated from any other parameters (defined as "accept-extension" |
paul@2 | 365 | # parameters) by a quality parameter. |
paul@2 | 366 | |
paul@2 | 367 | fparts = accept_regexp.split(field) |
paul@2 | 368 | |
paul@2 | 369 | # The first part is always the media type. |
paul@2 | 370 | |
paul@2 | 371 | media_type = fparts[0].strip() |
paul@2 | 372 | |
paul@2 | 373 | # Any other parts can be interpreted as extension parameters. |
paul@2 | 374 | |
paul@2 | 375 | if len(fparts) > 1: |
paul@2 | 376 | fparts = ("q=" + ";q=".join(fparts[1:])).split(";") |
paul@2 | 377 | else: |
paul@2 | 378 | fparts = [] |
paul@2 | 379 | |
paul@2 | 380 | # Each field in the preferences can incorporate parameters separated by |
paul@2 | 381 | # semicolon characters. |
paul@2 | 382 | |
paul@2 | 383 | parameters = getMappingFromParameterStrings(fparts) |
paul@2 | 384 | media_range = MediaRange(media_type, parameters) |
paul@2 | 385 | preferences.append(media_range) |
paul@2 | 386 | |
paul@2 | 387 | return ContentPreferences(preferences) |
paul@2 | 388 | |
paul@2 | 389 | class ContentPreferences: |
paul@2 | 390 | |
paul@2 | 391 | "A wrapper around content preference information." |
paul@2 | 392 | |
paul@2 | 393 | def __init__(self, preferences): |
paul@2 | 394 | self.preferences = preferences |
paul@2 | 395 | |
paul@2 | 396 | def __iter__(self): |
paul@2 | 397 | return iter(self.preferences) |
paul@2 | 398 | |
paul@2 | 399 | def get_ordered(self, by_quality=0): |
paul@2 | 400 | |
paul@2 | 401 | """ |
paul@2 | 402 | Return a list of content/media types in descending order of preference. |
paul@2 | 403 | If 'by_quality' is set to a true value, the "q" value will be used as |
paul@2 | 404 | the primary measure of preference; otherwise, only the specificity will |
paul@2 | 405 | be considered. |
paul@2 | 406 | """ |
paul@2 | 407 | |
paul@2 | 408 | ordered = {} |
paul@2 | 409 | |
paul@2 | 410 | for media_range in self.preferences: |
paul@2 | 411 | specificity = media_range.get_specificity() |
paul@2 | 412 | |
paul@2 | 413 | if by_quality: |
paul@2 | 414 | q = float(media_range.accept_parameters.get("q", "1")) |
paul@2 | 415 | key = q, specificity |
paul@2 | 416 | else: |
paul@2 | 417 | key = specificity |
paul@2 | 418 | |
paul@2 | 419 | if not ordered.has_key(key): |
paul@2 | 420 | ordered[key] = [] |
paul@2 | 421 | |
paul@2 | 422 | ordered[key].append(media_range) |
paul@2 | 423 | |
paul@2 | 424 | # Return the preferences in descending order of quality and specificity. |
paul@2 | 425 | |
paul@2 | 426 | keys = ordered.keys() |
paul@2 | 427 | keys.sort(reverse=True) |
paul@2 | 428 | return [ordered[key] for key in keys] |
paul@2 | 429 | |
paul@2 | 430 | def get_preferred_type(self, available): |
paul@2 | 431 | |
paul@2 | 432 | """ |
paul@2 | 433 | Return the preferred content/media type from those in the 'available' |
paul@2 | 434 | list, given the known preferences. |
paul@2 | 435 | """ |
paul@2 | 436 | |
paul@2 | 437 | matches = {} |
paul@2 | 438 | available = set(available[:]) |
paul@2 | 439 | |
paul@2 | 440 | for level in self.get_ordered(): |
paul@2 | 441 | for media_range in level: |
paul@2 | 442 | |
paul@2 | 443 | # Attempt to match available types. |
paul@2 | 444 | |
paul@2 | 445 | found = set() |
paul@2 | 446 | for available_type in available: |
paul@2 | 447 | if media_range.permits(available_type): |
paul@2 | 448 | q = float(media_range.accept_parameters.get("q", "1")) |
paul@2 | 449 | if not matches.has_key(q): |
paul@2 | 450 | matches[q] = [] |
paul@2 | 451 | matches[q].append(available_type) |
paul@2 | 452 | found.add(available_type) |
paul@2 | 453 | |
paul@2 | 454 | # Stop looking for matches for matched available types. |
paul@2 | 455 | |
paul@2 | 456 | if found: |
paul@2 | 457 | available.difference_update(found) |
paul@2 | 458 | |
paul@2 | 459 | # Sort the matches in descending order of quality. |
paul@2 | 460 | |
paul@2 | 461 | all_q = matches.keys() |
paul@2 | 462 | |
paul@2 | 463 | if all_q: |
paul@2 | 464 | all_q.sort(reverse=True) |
paul@2 | 465 | return matches[all_q[0]] |
paul@2 | 466 | else: |
paul@2 | 467 | return None |
paul@2 | 468 | |
paul@1 | 469 | # Page access functions. |
paul@1 | 470 | |
paul@1 | 471 | def getPageURL(page): |
paul@1 | 472 | |
paul@1 | 473 | "Return the URL of the given 'page'." |
paul@1 | 474 | |
paul@1 | 475 | request = page.request |
paul@1 | 476 | return request.getQualifiedURL(page.url(request, relative=0)) |
paul@1 | 477 | |
paul@1 | 478 | def getFormat(page): |
paul@1 | 479 | |
paul@1 | 480 | "Get the format used on the given 'page'." |
paul@1 | 481 | |
paul@1 | 482 | return page.pi["format"] |
paul@1 | 483 | |
paul@1 | 484 | def getMetadata(page): |
paul@1 | 485 | |
paul@1 | 486 | """ |
paul@1 | 487 | Return a dictionary containing items describing for the given 'page' the |
paul@1 | 488 | page's "created" time, "last-modified" time, "sequence" (or revision number) |
paul@1 | 489 | and the "last-comment" made about the last edit. |
paul@1 | 490 | """ |
paul@1 | 491 | |
paul@1 | 492 | request = page.request |
paul@1 | 493 | |
paul@1 | 494 | # Get the initial revision of the page. |
paul@1 | 495 | |
paul@1 | 496 | revisions = page.getRevList() |
paul@1 | 497 | event_page_initial = Page(request, page.page_name, rev=revisions[-1]) |
paul@1 | 498 | |
paul@1 | 499 | # Get the created and last modified times. |
paul@1 | 500 | |
paul@1 | 501 | initial_revision = getPageRevision(event_page_initial) |
paul@1 | 502 | |
paul@1 | 503 | metadata = {} |
paul@1 | 504 | metadata["created"] = initial_revision["timestamp"] |
paul@1 | 505 | latest_revision = getPageRevision(page) |
paul@1 | 506 | metadata["last-modified"] = latest_revision["timestamp"] |
paul@1 | 507 | metadata["sequence"] = len(revisions) - 1 |
paul@1 | 508 | metadata["last-comment"] = latest_revision["comment"] |
paul@1 | 509 | |
paul@1 | 510 | return metadata |
paul@0 | 511 | |
paul@0 | 512 | def getPageRevision(page): |
paul@0 | 513 | |
paul@0 | 514 | "Return the revision details dictionary for the given 'page'." |
paul@0 | 515 | |
paul@0 | 516 | # From Page.edit_info... |
paul@0 | 517 | |
paul@0 | 518 | if hasattr(page, "editlog_entry"): |
paul@0 | 519 | line = page.editlog_entry() |
paul@0 | 520 | else: |
paul@0 | 521 | line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x |
paul@0 | 522 | |
paul@0 | 523 | # Similar to Page.mtime_usecs behaviour... |
paul@0 | 524 | |
paul@0 | 525 | if line: |
paul@0 | 526 | timestamp = line.ed_time_usecs |
paul@0 | 527 | mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x |
paul@0 | 528 | comment = line.comment |
paul@0 | 529 | else: |
paul@0 | 530 | mtime = 0 |
paul@0 | 531 | comment = "" |
paul@0 | 532 | |
paul@0 | 533 | # Leave the time zone empty. |
paul@0 | 534 | |
paul@0 | 535 | return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + (None,)), "comment" : comment} |
paul@0 | 536 | |
paul@11 | 537 | # Page parsing and formatting of embedded content. |
paul@11 | 538 | |
paul@11 | 539 | def getParserClass(request, format): |
paul@11 | 540 | |
paul@11 | 541 | """ |
paul@11 | 542 | Return a parser class using the 'request' for the given 'format', returning |
paul@11 | 543 | a plain text parser if no parser can be found for the specified 'format'. |
paul@11 | 544 | """ |
paul@11 | 545 | |
paul@11 | 546 | try: |
paul@11 | 547 | return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain") |
paul@11 | 548 | except wikiutil.PluginMissingError: |
paul@11 | 549 | return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain") |
paul@11 | 550 | |
paul@11 | 551 | def redirectedOutput(request, parser, fmt, **kw): |
paul@11 | 552 | |
paul@11 | 553 | "A fixed version of the request method of the same name." |
paul@11 | 554 | |
paul@11 | 555 | buf = StringIO() |
paul@11 | 556 | request.redirect(buf) |
paul@11 | 557 | try: |
paul@11 | 558 | parser.format(fmt, **kw) |
paul@11 | 559 | if hasattr(fmt, "flush"): |
paul@11 | 560 | buf.write(fmt.flush(True)) |
paul@11 | 561 | finally: |
paul@11 | 562 | request.redirect() |
paul@11 | 563 | text = buf.getvalue() |
paul@11 | 564 | buf.close() |
paul@11 | 565 | return text |
paul@11 | 566 | |
paul@0 | 567 | # User interface functions. |
paul@0 | 568 | |
paul@0 | 569 | def getParameter(request, name, default=None): |
paul@0 | 570 | |
paul@0 | 571 | """ |
paul@0 | 572 | Using the given 'request', return the value of the parameter with the given |
paul@0 | 573 | 'name', returning the optional 'default' (or None) if no value was supplied |
paul@0 | 574 | in the 'request'. |
paul@0 | 575 | """ |
paul@0 | 576 | |
paul@0 | 577 | return get_form(request).get(name, [default])[0] |
paul@0 | 578 | |
paul@0 | 579 | def getQualifiedParameter(request, prefix, argname, default=None): |
paul@0 | 580 | |
paul@0 | 581 | """ |
paul@0 | 582 | Using the given 'request', 'prefix' and 'argname', retrieve the value of the |
paul@0 | 583 | qualified parameter, returning the optional 'default' (or None) if no value |
paul@0 | 584 | was supplied in the 'request'. |
paul@0 | 585 | """ |
paul@0 | 586 | |
paul@0 | 587 | argname = getQualifiedParameterName(prefix, argname) |
paul@0 | 588 | return getParameter(request, argname, default) |
paul@0 | 589 | |
paul@0 | 590 | def getQualifiedParameterName(prefix, argname): |
paul@0 | 591 | |
paul@0 | 592 | """ |
paul@0 | 593 | Return the qualified parameter name using the given 'prefix' and 'argname'. |
paul@0 | 594 | """ |
paul@0 | 595 | |
paul@0 | 596 | if prefix is None: |
paul@0 | 597 | return argname |
paul@0 | 598 | else: |
paul@0 | 599 | return "%s-%s" % (prefix, argname) |
paul@0 | 600 | |
paul@0 | 601 | # Page-related functions. |
paul@0 | 602 | |
paul@0 | 603 | def getPrettyPageName(page): |
paul@0 | 604 | |
paul@0 | 605 | "Return a nicely formatted title/name for the given 'page'." |
paul@0 | 606 | |
paul@0 | 607 | title = page.split_title(force=1) |
paul@0 | 608 | return getPrettyTitle(title) |
paul@0 | 609 | |
paul@3 | 610 | def linkToPage(request, page, text, query_string=None, **kw): |
paul@0 | 611 | |
paul@0 | 612 | """ |
paul@0 | 613 | Using 'request', return a link to 'page' with the given link 'text' and |
paul@0 | 614 | optional 'query_string'. |
paul@0 | 615 | """ |
paul@0 | 616 | |
paul@0 | 617 | text = wikiutil.escape(text) |
paul@3 | 618 | return page.link_to_raw(request, text, query_string, **kw) |
paul@0 | 619 | |
paul@0 | 620 | def linkToResource(url, request, text, query_string=None): |
paul@0 | 621 | |
paul@0 | 622 | """ |
paul@0 | 623 | Using 'request', return a link to 'url' with the given link 'text' and |
paul@0 | 624 | optional 'query_string'. |
paul@0 | 625 | """ |
paul@0 | 626 | |
paul@0 | 627 | if query_string: |
paul@0 | 628 | query_string = wikiutil.makeQueryString(query_string) |
paul@0 | 629 | url = "%s?%s" % (url, query_string) |
paul@0 | 630 | |
paul@0 | 631 | formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter |
paul@0 | 632 | |
paul@0 | 633 | output = [] |
paul@0 | 634 | output.append(formatter.url(1, url)) |
paul@0 | 635 | output.append(formatter.text(text)) |
paul@0 | 636 | output.append(formatter.url(0)) |
paul@0 | 637 | return "".join(output) |
paul@0 | 638 | |
paul@0 | 639 | def getFullPageName(parent, title): |
paul@0 | 640 | |
paul@0 | 641 | """ |
paul@0 | 642 | Return a full page name from the given 'parent' page (can be empty or None) |
paul@0 | 643 | and 'title' (a simple page name). |
paul@0 | 644 | """ |
paul@0 | 645 | |
paul@0 | 646 | if parent: |
paul@0 | 647 | return "%s/%s" % (parent.rstrip("/"), title) |
paul@0 | 648 | else: |
paul@0 | 649 | return title |
paul@0 | 650 | |
paul@0 | 651 | # vim: tabstop=4 expandtab shiftwidth=4 |