paul@0 | 1 | # -*- coding: iso-8859-1 -*- |
paul@0 | 2 | """ |
paul@0 | 3 | MoinMoin - MoinShare library |
paul@0 | 4 | |
paul@17 | 5 | @copyright: 2011, 2012, 2013 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@17 | 9 | from ContentTypeSupport import getContentPreferences |
paul@33 | 10 | from DateSupport import getCurrentTime, getDateTimeFromRFC2822 |
paul@0 | 11 | from MoinSupport import * |
paul@15 | 12 | from MoinMoin import wikiutil |
paul@33 | 13 | from email.parser import Parser |
paul@0 | 14 | import re |
paul@0 | 15 | |
paul@25 | 16 | try: |
paul@25 | 17 | from cStringIO import StringIO |
paul@25 | 18 | except ImportError: |
paul@25 | 19 | from StringIO import StringIO |
paul@25 | 20 | |
paul@0 | 21 | escape = wikiutil.escape |
paul@16 | 22 | _getFragments = getFragments |
paul@0 | 23 | |
paul@0 | 24 | __version__ = "0.1" |
paul@0 | 25 | |
paul@0 | 26 | # More Moin 1.9 compatibility functions. |
paul@0 | 27 | |
paul@0 | 28 | def has_member(request, groupname, username): |
paul@0 | 29 | if hasattr(request.dicts, "has_member"): |
paul@0 | 30 | return request.dicts.has_member(groupname, username) |
paul@0 | 31 | else: |
paul@0 | 32 | return username in request.dicts.get(groupname, []) |
paul@0 | 33 | |
paul@3 | 34 | # Fragments employ a "moinshare" attribute. |
paul@3 | 35 | |
paul@3 | 36 | fragment_attribute = "moinshare" |
paul@2 | 37 | |
paul@16 | 38 | def getFragments(s): |
paul@25 | 39 | |
paul@25 | 40 | "Return all fragments in 's' having the MoinShare fragment attribute." |
paul@25 | 41 | |
paul@2 | 42 | fragments = [] |
paul@16 | 43 | for format, attributes, body in _getFragments(s): |
paul@16 | 44 | if attributes.has_key(fragment_attribute): |
paul@16 | 45 | fragments.append((format, attributes, body)) |
paul@2 | 46 | return fragments |
paul@2 | 47 | |
paul@9 | 48 | def getOutputTypes(request, format): |
paul@9 | 49 | |
paul@9 | 50 | """ |
paul@9 | 51 | Using the 'request' and the 'format' of a fragment, return the media types |
paul@9 | 52 | available for the fragment. |
paul@9 | 53 | """ |
paul@9 | 54 | |
paul@9 | 55 | # This uses an extended parser API method if available. |
paul@9 | 56 | |
paul@9 | 57 | parser = getParserClass(request, format) |
paul@9 | 58 | if hasattr(parser, "getOutputTypes"): |
paul@9 | 59 | return parser.getOutputTypes() |
paul@9 | 60 | else: |
paul@9 | 61 | return ["text/html"] |
paul@9 | 62 | |
paul@9 | 63 | def getPreferredOutputTypes(request, mimetypes): |
paul@9 | 64 | |
paul@9 | 65 | """ |
paul@9 | 66 | Using the 'request', perform content negotiation, obtaining mimetypes common |
paul@9 | 67 | to the fragment (given by 'mimetypes') and the client (found in the Accept |
paul@9 | 68 | header). |
paul@9 | 69 | """ |
paul@9 | 70 | |
paul@9 | 71 | accept = getHeader(request, "Accept", "HTTP") |
paul@12 | 72 | if accept: |
paul@12 | 73 | prefs = getContentPreferences(accept) |
paul@12 | 74 | return prefs.get_preferred_types(mimetypes) |
paul@12 | 75 | else: |
paul@12 | 76 | return mimetypes |
paul@9 | 77 | |
paul@9 | 78 | def getUpdatedTime(metadata): |
paul@9 | 79 | |
paul@9 | 80 | """ |
paul@9 | 81 | Return the last updated time based on the given 'metadata', using the |
paul@9 | 82 | current time if no explicit last modified time is specified. |
paul@9 | 83 | """ |
paul@9 | 84 | |
paul@9 | 85 | # NOTE: We could attempt to get the last edit time of a fragment. |
paul@9 | 86 | |
paul@9 | 87 | latest_timestamp = metadata.get("last-modified") |
paul@9 | 88 | if latest_timestamp: |
paul@33 | 89 | return latest_timestamp |
paul@9 | 90 | else: |
paul@33 | 91 | return getCurrentTime() |
paul@9 | 92 | |
paul@25 | 93 | def getUpdateSources(request, sources_page): |
paul@25 | 94 | |
paul@25 | 95 | """ |
paul@25 | 96 | Using the 'request', return the update sources defined on the given |
paul@25 | 97 | 'sources_page'. |
paul@25 | 98 | """ |
paul@25 | 99 | |
paul@25 | 100 | # Remote sources are accessed via dictionary page definitions. |
paul@25 | 101 | |
paul@25 | 102 | return getWikiDict(sources_page, request) |
paul@25 | 103 | |
paul@30 | 104 | # Entry/update classes. |
paul@30 | 105 | |
paul@30 | 106 | class Update: |
paul@30 | 107 | |
paul@30 | 108 | "A feed update entry." |
paul@30 | 109 | |
paul@30 | 110 | def __init__(self): |
paul@30 | 111 | self.title = None |
paul@30 | 112 | self.link = None |
paul@30 | 113 | self.content = None |
paul@30 | 114 | self.content_type = None |
paul@30 | 115 | self.updated = None |
paul@30 | 116 | |
paul@30 | 117 | # Page-related attributes. |
paul@30 | 118 | |
paul@30 | 119 | self.fragment = None |
paul@30 | 120 | self.preferred = None |
paul@30 | 121 | |
paul@33 | 122 | # Message-related attributes. |
paul@33 | 123 | |
paul@33 | 124 | self.parts = None |
paul@33 | 125 | |
paul@30 | 126 | def __cmp__(self, other): |
paul@30 | 127 | if self.updated is None and other.updated is not None: |
paul@30 | 128 | return 1 |
paul@30 | 129 | elif self.updated is not None and other.updated is None: |
paul@30 | 130 | return -1 |
paul@30 | 131 | else: |
paul@30 | 132 | return cmp(self.updated, other.updated) |
paul@30 | 133 | |
paul@30 | 134 | # Update retrieval from pages. |
paul@30 | 135 | |
paul@30 | 136 | def getUpdatesFromPage(page, request): |
paul@25 | 137 | |
paul@25 | 138 | """ |
paul@30 | 139 | Get updates from the given 'page' using the 'request'. A list of update |
paul@30 | 140 | objects is returned. |
paul@25 | 141 | """ |
paul@25 | 142 | |
paul@25 | 143 | updates = [] |
paul@25 | 144 | |
paul@25 | 145 | # NOTE: Use the updated datetime from the page for updates. |
paul@25 | 146 | # NOTE: The published and updated details would need to be deduced from |
paul@25 | 147 | # NOTE: the page history instead of being taken from the page as a whole. |
paul@25 | 148 | |
paul@25 | 149 | metadata = getMetadata(page) |
paul@25 | 150 | updated = getUpdatedTime(metadata) |
paul@25 | 151 | |
paul@25 | 152 | # Get the fragment regions for the page. |
paul@25 | 153 | |
paul@25 | 154 | for n, (format, attributes, body) in enumerate(getFragments(page.get_raw_body())): |
paul@25 | 155 | |
paul@33 | 156 | update = Update() |
paul@33 | 157 | |
paul@25 | 158 | # Produce a fragment identifier. |
paul@25 | 159 | # NOTE: Choose a more robust identifier where none is explicitly given. |
paul@25 | 160 | |
paul@30 | 161 | update.fragment = attributes.get("fragment", str(n)) |
paul@30 | 162 | update.title = attributes.get("summary", "Update #%d" % n) |
paul@25 | 163 | |
paul@25 | 164 | # Get the preferred content types available for the fragment. |
paul@25 | 165 | |
paul@30 | 166 | update.preferred = getPreferredOutputTypes(request, getOutputTypes(request, format)) |
paul@25 | 167 | |
paul@25 | 168 | # Try and obtain some suitable content for the entry. |
paul@25 | 169 | # NOTE: Could potentially get a summary for the fragment. |
paul@25 | 170 | |
paul@30 | 171 | update.content = None |
paul@25 | 172 | |
paul@30 | 173 | if "text/html" in update.preferred: |
paul@25 | 174 | parser_cls = getParserClass(request, format) |
paul@25 | 175 | parser = parser_cls(body, request) |
paul@25 | 176 | |
paul@25 | 177 | if format == "html": |
paul@30 | 178 | update.content = body |
paul@25 | 179 | elif hasattr(parser, "formatForOutputType"): |
paul@25 | 180 | s = StringIO() |
paul@25 | 181 | parser.formatForOutputType("text/html", write=s.write) |
paul@30 | 182 | update.content = s.getvalue() |
paul@25 | 183 | else: |
paul@25 | 184 | fmt = request.html_formatter |
paul@25 | 185 | fmt.setPage(page) |
paul@30 | 186 | update.content = formatText(body, request, fmt, parser_cls) |
paul@30 | 187 | |
paul@32 | 188 | update.content_type = "text/html" |
paul@25 | 189 | |
paul@30 | 190 | update.link = page.url(request) |
paul@30 | 191 | update.updated = updated |
paul@30 | 192 | |
paul@30 | 193 | updates.append(update) |
paul@25 | 194 | |
paul@25 | 195 | return updates |
paul@25 | 196 | |
paul@33 | 197 | # Update retrieval from message stores. |
paul@33 | 198 | |
paul@33 | 199 | def getUpdatesFromStore(page, request): |
paul@33 | 200 | |
paul@33 | 201 | """ |
paul@33 | 202 | Get updates from the message store associated with the given 'page' using |
paul@33 | 203 | the 'request'. A list of update objects is returned. |
paul@33 | 204 | """ |
paul@33 | 205 | |
paul@33 | 206 | updates = [] |
paul@33 | 207 | |
paul@33 | 208 | metadata = getMetadata(page) |
paul@33 | 209 | updated = getUpdatedTime(metadata) |
paul@33 | 210 | |
paul@33 | 211 | store = ItemStore(page, "messages", "message-locks") |
paul@33 | 212 | |
paul@33 | 213 | for n, message_text in enumerate(iter(store)): |
paul@33 | 214 | |
paul@33 | 215 | update = Update() |
paul@33 | 216 | message = Parser().parse(StringIO(message_text)) |
paul@33 | 217 | |
paul@33 | 218 | # Produce a fragment identifier. |
paul@33 | 219 | |
paul@33 | 220 | update.fragment = update.updated = getDateTimeFromRFC2822(message.get("date")) |
paul@33 | 221 | update.title = message.get("subject", "Update #%d" % n) |
paul@33 | 222 | |
paul@33 | 223 | # Determine whether the message has several representations. |
paul@33 | 224 | |
paul@33 | 225 | if not message.is_multipart(): |
paul@33 | 226 | update.content = message.get_payload() |
paul@33 | 227 | update.content_type = message.get_content_type() |
paul@33 | 228 | else: |
paul@33 | 229 | update.parts = message.get_payload() |
paul@33 | 230 | |
paul@33 | 231 | updates.append(update) |
paul@33 | 232 | |
paul@33 | 233 | return updates |
paul@33 | 234 | |
paul@31 | 235 | # Source management. |
paul@31 | 236 | |
paul@31 | 237 | def getUpdateSources(pagename, request): |
paul@31 | 238 | |
paul@31 | 239 | "Return the update sources from the given 'pagename' using the 'request'." |
paul@31 | 240 | |
paul@31 | 241 | sources = {} |
paul@31 | 242 | |
paul@31 | 243 | source_definitions = getWikiDict(pagename, request) |
paul@31 | 244 | |
paul@31 | 245 | if source_definitions: |
paul@31 | 246 | for name, value in source_definitions.items(): |
paul@31 | 247 | sources[name] = getSourceParameters(value) |
paul@31 | 248 | |
paul@31 | 249 | return sources |
paul@31 | 250 | |
paul@31 | 251 | def getSourceParameters(source_definition): |
paul@31 | 252 | |
paul@31 | 253 | "Return the parameters from the given 'source_definition' string." |
paul@31 | 254 | |
paul@31 | 255 | parameters = {} |
paul@31 | 256 | unqualified = ("type", "location") |
paul@31 | 257 | |
paul@31 | 258 | for arg in source_definition.split(): |
paul@31 | 259 | try: |
paul@31 | 260 | argname, argvalue = arg.split("=", 1) |
paul@31 | 261 | |
paul@31 | 262 | # Detect unlikely parameter names. |
paul@31 | 263 | |
paul@31 | 264 | if not argname.isalpha(): |
paul@31 | 265 | raise ValueError |
paul@31 | 266 | |
paul@31 | 267 | parameters[argname] = argvalue |
paul@31 | 268 | |
paul@31 | 269 | # Unqualified parameters are assumed to be one of a recognised set. |
paul@31 | 270 | |
paul@31 | 271 | except ValueError: |
paul@31 | 272 | for argname in unqualified: |
paul@31 | 273 | if not parameters.has_key(argname): |
paul@31 | 274 | parameters[argname] = arg |
paul@31 | 275 | break |
paul@31 | 276 | |
paul@31 | 277 | return parameters |
paul@31 | 278 | |
paul@0 | 279 | # vim: tabstop=4 expandtab shiftwidth=4 |