paul@0 | 1 | # -*- coding: iso-8859-1 -*- |
paul@0 | 2 | """ |
paul@0 | 3 | MoinMoin - MoinShare library |
paul@0 | 4 | |
paul@0 | 5 | @copyright: 2011, 2012 by Paul Boddie <paul@boddie.org.uk> |
paul@0 | 6 | @license: GNU GPL (v2 or later), see COPYING.txt for details. |
paul@0 | 7 | """ |
paul@0 | 8 | |
paul@0 | 9 | from MoinSupport import * |
paul@9 | 10 | from MoinMoin import config, wikiutil |
paul@9 | 11 | from DateSupport import getCurrentTime |
paul@0 | 12 | import re |
paul@0 | 13 | |
paul@0 | 14 | escape = wikiutil.escape |
paul@0 | 15 | |
paul@0 | 16 | __version__ = "0.1" |
paul@0 | 17 | |
paul@0 | 18 | # More Moin 1.9 compatibility functions. |
paul@0 | 19 | |
paul@0 | 20 | def has_member(request, groupname, username): |
paul@0 | 21 | if hasattr(request.dicts, "has_member"): |
paul@0 | 22 | return request.dicts.has_member(groupname, username) |
paul@0 | 23 | else: |
paul@0 | 24 | return username in request.dicts.get(groupname, []) |
paul@0 | 25 | |
paul@0 | 26 | # Extraction of shared fragments. |
paul@0 | 27 | |
paul@0 | 28 | marker_regexp_str = r"([{]{3,}|[}]{3,})" |
paul@0 | 29 | marker_regexp = re.compile(marker_regexp_str, re.MULTILINE | re.DOTALL) # {{{... or }}}... |
paul@0 | 30 | |
paul@3 | 31 | # Fragments employ a "moinshare" attribute. |
paul@3 | 32 | |
paul@3 | 33 | fragment_prelude = "#!" |
paul@3 | 34 | fragment_attribute = "moinshare" |
paul@2 | 35 | |
paul@0 | 36 | def getRegions(s): |
paul@0 | 37 | |
paul@0 | 38 | "Parse the string 's', returning a list of shared regions." |
paul@0 | 39 | |
paul@0 | 40 | regions = [] |
paul@0 | 41 | marker = None |
paul@0 | 42 | is_region = True |
paul@0 | 43 | |
paul@0 | 44 | for match_text in marker_regexp.split(s): |
paul@0 | 45 | |
paul@0 | 46 | # Capture section text. |
paul@0 | 47 | |
paul@0 | 48 | if is_region and marker: |
paul@0 | 49 | regions[-1] += match_text |
paul@0 | 50 | |
paul@0 | 51 | # Handle section markers. |
paul@0 | 52 | |
paul@0 | 53 | elif not is_region: |
paul@0 | 54 | |
paul@0 | 55 | # Close any open sections, returning to exposed text regions. |
paul@0 | 56 | |
paul@0 | 57 | if marker: |
paul@0 | 58 | if match_text.startswith("}") and len(marker) == len(match_text): |
paul@0 | 59 | marker = None |
paul@0 | 60 | |
paul@0 | 61 | # Without a current marker, start a section if an appropriate marker |
paul@0 | 62 | # is given. |
paul@0 | 63 | |
paul@0 | 64 | elif match_text.startswith("{"): |
paul@0 | 65 | marker = match_text |
paul@0 | 66 | regions.append("") |
paul@0 | 67 | |
paul@0 | 68 | # Markers and section text are added to the current region. |
paul@0 | 69 | |
paul@0 | 70 | regions[-1] += match_text |
paul@0 | 71 | |
paul@0 | 72 | # Exposed text is ignored. |
paul@0 | 73 | |
paul@0 | 74 | # The match text alternates between text between markers and the markers |
paul@0 | 75 | # themselves. |
paul@0 | 76 | |
paul@0 | 77 | is_region = not is_region |
paul@0 | 78 | |
paul@0 | 79 | return regions |
paul@0 | 80 | |
paul@2 | 81 | def getFragmentsFromRegions(regions): |
paul@2 | 82 | |
paul@2 | 83 | """ |
paul@2 | 84 | Return fragments from the given 'regions', each having the form |
paul@4 | 85 | (format, arguments, body text). |
paul@2 | 86 | """ |
paul@2 | 87 | |
paul@2 | 88 | fragments = [] |
paul@2 | 89 | |
paul@2 | 90 | for region in regions: |
paul@4 | 91 | body = region.lstrip("{").rstrip("}").lstrip() |
paul@2 | 92 | if body.startswith(fragment_prelude): |
paul@2 | 93 | arguments, body = body[len(fragment_prelude):].split("\n", 1) |
paul@4 | 94 | |
paul@4 | 95 | # Get any parser/format declaration. |
paul@4 | 96 | |
paul@4 | 97 | if arguments and not arguments[0].isspace(): |
paul@4 | 98 | format, arguments = arguments.split(None, 1) |
paul@4 | 99 | else: |
paul@4 | 100 | format = None |
paul@4 | 101 | |
paul@4 | 102 | # Get the attributes/arguments for the region. |
paul@4 | 103 | |
paul@3 | 104 | attributes = parseAttributes(arguments, False) |
paul@4 | 105 | |
paul@4 | 106 | # If the format is the MoinShare attribute, move it into the |
paul@4 | 107 | # dictionary. |
paul@4 | 108 | |
paul@4 | 109 | if format.lower() == fragment_attribute: |
paul@4 | 110 | attributes[fragment_attribute] = True |
paul@4 | 111 | format = None |
paul@4 | 112 | |
paul@4 | 113 | # Only collect appropriate regions. |
paul@4 | 114 | |
paul@3 | 115 | if attributes.has_key(fragment_attribute): |
paul@4 | 116 | fragments.append((format, attributes, body)) |
paul@2 | 117 | |
paul@2 | 118 | return fragments |
paul@2 | 119 | |
paul@2 | 120 | def getFragments(s): |
paul@2 | 121 | |
paul@2 | 122 | """ |
paul@2 | 123 | Return fragments for the given string 's', each having the form |
paul@2 | 124 | (arguments, body text). |
paul@2 | 125 | """ |
paul@2 | 126 | |
paul@2 | 127 | return getFragmentsFromRegions(getRegions(s)) |
paul@2 | 128 | |
paul@9 | 129 | def getOutputTypes(request, format): |
paul@9 | 130 | |
paul@9 | 131 | """ |
paul@9 | 132 | Using the 'request' and the 'format' of a fragment, return the media types |
paul@9 | 133 | available for the fragment. |
paul@9 | 134 | """ |
paul@9 | 135 | |
paul@9 | 136 | # This uses an extended parser API method if available. |
paul@9 | 137 | |
paul@9 | 138 | parser = getParserClass(request, format) |
paul@9 | 139 | if hasattr(parser, "getOutputTypes"): |
paul@9 | 140 | return parser.getOutputTypes() |
paul@9 | 141 | else: |
paul@9 | 142 | return ["text/html"] |
paul@9 | 143 | |
paul@9 | 144 | def getPreferredOutputTypes(request, mimetypes): |
paul@9 | 145 | |
paul@9 | 146 | """ |
paul@9 | 147 | Using the 'request', perform content negotiation, obtaining mimetypes common |
paul@9 | 148 | to the fragment (given by 'mimetypes') and the client (found in the Accept |
paul@9 | 149 | header). |
paul@9 | 150 | """ |
paul@9 | 151 | |
paul@9 | 152 | accept = getHeader(request, "Accept", "HTTP") |
paul@12 | 153 | if accept: |
paul@12 | 154 | prefs = getContentPreferences(accept) |
paul@12 | 155 | return prefs.get_preferred_types(mimetypes) |
paul@12 | 156 | else: |
paul@12 | 157 | return mimetypes |
paul@9 | 158 | |
paul@9 | 159 | def writeHeaders(request, mimetype, metadata, status=None): |
paul@9 | 160 | |
paul@9 | 161 | """ |
paul@9 | 162 | Using the 'request', write resource headers using the given 'mimetype', |
paul@9 | 163 | based on the given 'metadata'. If the optional 'status' is specified, set |
paul@9 | 164 | the status header to the given value. |
paul@9 | 165 | """ |
paul@9 | 166 | |
paul@9 | 167 | send_headers = get_send_headers(request) |
paul@9 | 168 | |
paul@9 | 169 | # Define headers. |
paul@9 | 170 | |
paul@9 | 171 | headers = ["Content-Type: %s; charset=%s" % (mimetype, config.charset)] |
paul@9 | 172 | |
paul@9 | 173 | # Define the last modified time. |
paul@9 | 174 | # NOTE: Consider using request.httpDate. |
paul@9 | 175 | |
paul@9 | 176 | latest_timestamp = metadata.get("last-modified") |
paul@9 | 177 | if latest_timestamp: |
paul@9 | 178 | headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string()) |
paul@9 | 179 | |
paul@9 | 180 | if status: |
paul@9 | 181 | headers.append("Status: %s" % status) |
paul@9 | 182 | |
paul@9 | 183 | send_headers(headers) |
paul@9 | 184 | |
paul@9 | 185 | def getUpdatedTime(metadata): |
paul@9 | 186 | |
paul@9 | 187 | """ |
paul@9 | 188 | Return the last updated time based on the given 'metadata', using the |
paul@9 | 189 | current time if no explicit last modified time is specified. |
paul@9 | 190 | """ |
paul@9 | 191 | |
paul@9 | 192 | # NOTE: We could attempt to get the last edit time of a fragment. |
paul@9 | 193 | |
paul@9 | 194 | latest_timestamp = metadata.get("last-modified") |
paul@9 | 195 | if latest_timestamp: |
paul@9 | 196 | return latest_timestamp.as_ISO8601_datetime_string() |
paul@9 | 197 | else: |
paul@9 | 198 | return getCurrentTime().as_ISO8601_datetime_string() |
paul@9 | 199 | |
paul@0 | 200 | # vim: tabstop=4 expandtab shiftwidth=4 |