paul@347 | 1 | # -*- coding: iso-8859-1 -*- |
paul@347 | 2 | """ |
paul@347 | 3 | MoinMoin - EventAggregator event formatting |
paul@347 | 4 | |
paul@347 | 5 | @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk> |
paul@347 | 6 | @copyright: 2000-2004 Juergen Hermann <jh@web.de>, |
paul@347 | 7 | 2005-2008 MoinMoin:ThomasWaldmann. |
paul@347 | 8 | @license: GNU GPL (v2 or later), see COPYING.txt for details. |
paul@347 | 9 | """ |
paul@347 | 10 | |
paul@347 | 11 | from MoinSupport import * |
paul@347 | 12 | from MoinMoin.wikiutil import escape |
paul@347 | 13 | |
paul@347 | 14 | try: |
paul@347 | 15 | import vCalendar |
paul@347 | 16 | except ImportError: |
paul@347 | 17 | vCalendar = None |
paul@347 | 18 | |
paul@347 | 19 | # Event-only formatting. |
paul@347 | 20 | |
paul@347 | 21 | def formatEvent(event, request, fmt, write=None): |
paul@347 | 22 | |
paul@347 | 23 | """ |
paul@347 | 24 | Format the given 'event' using the 'request' and formatter 'fmt'. If the |
paul@347 | 25 | 'write' parameter is specified, use it to write output. |
paul@347 | 26 | """ |
paul@347 | 27 | |
paul@347 | 28 | details = event.getDetails() |
paul@347 | 29 | raw_details = event.getRawDetails() |
paul@347 | 30 | write = write or request.write |
paul@347 | 31 | |
paul@347 | 32 | if details.has_key("fragment"): |
paul@347 | 33 | write(fmt.anchordef(details["fragment"])) |
paul@347 | 34 | |
paul@347 | 35 | # Promote any title to a heading above the event details. |
paul@347 | 36 | |
paul@347 | 37 | if raw_details.has_key("title"): |
paul@347 | 38 | write(formatText(raw_details["title"], request, fmt)) |
paul@347 | 39 | elif details.has_key("title"): |
paul@347 | 40 | write(fmt.heading(on=1, depth=1)) |
paul@347 | 41 | write(fmt.text(details["title"])) |
paul@347 | 42 | write(fmt.heading(on=0, depth=1)) |
paul@347 | 43 | |
paul@347 | 44 | # Produce a definition list for the rest of the details. |
paul@347 | 45 | |
paul@347 | 46 | write(fmt.definition_list(on=1)) |
paul@347 | 47 | |
paul@347 | 48 | for term in event.all_terms: |
paul@347 | 49 | if term == "title": |
paul@347 | 50 | continue |
paul@347 | 51 | |
paul@347 | 52 | raw_value = raw_details.get(term) |
paul@347 | 53 | value = details.get(term) |
paul@347 | 54 | |
paul@347 | 55 | if raw_value or value: |
paul@347 | 56 | write(fmt.definition_term(on=1)) |
paul@347 | 57 | write(fmt.text(term)) |
paul@347 | 58 | write(fmt.definition_term(on=0)) |
paul@347 | 59 | write(fmt.definition_desc(on=1)) |
paul@347 | 60 | |
paul@347 | 61 | # Try and use the raw details, if available. |
paul@347 | 62 | |
paul@347 | 63 | if raw_value: |
paul@347 | 64 | write(formatText(raw_value, request, fmt)) |
paul@347 | 65 | |
paul@347 | 66 | # Otherwise, format the processed details. |
paul@347 | 67 | |
paul@347 | 68 | else: |
paul@347 | 69 | if term in event.list_terms: |
paul@347 | 70 | write(", ".join([formatText(str(v), request, fmt) for v in value])) |
paul@347 | 71 | else: |
paul@366 | 72 | write(fmt.text(unicode(value))) |
paul@347 | 73 | |
paul@347 | 74 | write(fmt.definition_desc(on=0)) |
paul@347 | 75 | |
paul@347 | 76 | write(fmt.definition_list(on=0)) |
paul@347 | 77 | |
paul@347 | 78 | def formatEventsForOutputType(events, request, mimetype, parent=None, descriptions=None, latest_timestamp=None, write=None): |
paul@347 | 79 | |
paul@347 | 80 | """ |
paul@347 | 81 | Format the given 'events' using the 'request' for the given 'mimetype'. |
paul@347 | 82 | |
paul@347 | 83 | The optional 'parent' indicates the "natural" parent page of the events. Any |
paul@347 | 84 | event pages residing beneath the parent page will have their names |
paul@347 | 85 | reproduced as relative to the parent page. |
paul@347 | 86 | |
paul@347 | 87 | The optional 'descriptions' indicates the nature of any description given |
paul@347 | 88 | for events in the output resource. |
paul@347 | 89 | |
paul@347 | 90 | The optional 'latest_timestamp' indicates the timestamp of the latest edit |
paul@347 | 91 | of the page or event collection. |
paul@347 | 92 | |
paul@347 | 93 | If the 'write' parameter is specified, use it to write output. |
paul@347 | 94 | """ |
paul@347 | 95 | |
paul@347 | 96 | write = write or request.write |
paul@347 | 97 | |
paul@347 | 98 | # Start the collection. |
paul@347 | 99 | |
paul@347 | 100 | if mimetype == "text/calendar" and vCalendar is not None: |
paul@364 | 101 | _write = vCalendar.iterwrite(write=write).write |
paul@364 | 102 | _write("BEGIN", {}, "VCALENDAR") |
paul@364 | 103 | _write("PRODID", {}, "-//MoinMoin//EventAggregatorSummary") |
paul@364 | 104 | _write("VERSION", {}, "2.0") |
paul@347 | 105 | |
paul@347 | 106 | elif mimetype == "application/rss+xml": |
paul@347 | 107 | |
paul@347 | 108 | # Using the page name and the page URL in the title, link and |
paul@347 | 109 | # description. |
paul@347 | 110 | |
paul@347 | 111 | path_info = getPathInfo(request) |
paul@347 | 112 | |
paul@365 | 113 | write('<rss version="2.0">\n') |
paul@365 | 114 | write('<channel>\n') |
paul@365 | 115 | write('<title>%s</title>\n' % path_info[1:]) |
paul@365 | 116 | write('<link>%s%s</link>\n' % (request.getBaseURL(), path_info)) |
paul@365 | 117 | write('<description>Events published on %s%s</description>\n' % (request.getBaseURL(), path_info)) |
paul@347 | 118 | |
paul@347 | 119 | if latest_timestamp is not None: |
paul@365 | 120 | write('<lastBuildDate>%s</lastBuildDate>\n' % latest_timestamp.as_HTTP_datetime_string()) |
paul@347 | 121 | |
paul@347 | 122 | # Sort the events by start date, reversed. |
paul@347 | 123 | |
paul@347 | 124 | ordered_events = getOrderedEvents(events) |
paul@347 | 125 | ordered_events.reverse() |
paul@347 | 126 | events = ordered_events |
paul@347 | 127 | |
paul@347 | 128 | elif mimetype == "text/html": |
paul@347 | 129 | write('<html>') |
paul@347 | 130 | write('<body>') |
paul@347 | 131 | |
paul@347 | 132 | # Output the collection one by one. |
paul@347 | 133 | |
paul@347 | 134 | for event in events: |
paul@360 | 135 | formatEventForOutputType(event, request, mimetype, parent, descriptions, write) |
paul@347 | 136 | |
paul@347 | 137 | # End the collection. |
paul@347 | 138 | |
paul@347 | 139 | if mimetype == "text/calendar" and vCalendar is not None: |
paul@364 | 140 | _write("END", {}, "VCALENDAR") |
paul@347 | 141 | |
paul@347 | 142 | elif mimetype == "application/rss+xml": |
paul@365 | 143 | write('</channel>\n') |
paul@365 | 144 | write('</rss>\n') |
paul@347 | 145 | |
paul@347 | 146 | elif mimetype == "text/html": |
paul@347 | 147 | write('</body>') |
paul@347 | 148 | write('</html>') |
paul@347 | 149 | |
paul@347 | 150 | def formatEventForOutputType(event, request, mimetype, parent=None, descriptions=None, write=None): |
paul@347 | 151 | |
paul@347 | 152 | """ |
paul@347 | 153 | Format the given 'event' using the 'request' for the given 'mimetype'. |
paul@347 | 154 | |
paul@347 | 155 | The optional 'parent' indicates the "natural" parent page of the events. Any |
paul@347 | 156 | event pages residing beneath the parent page will have their names |
paul@347 | 157 | reproduced as relative to the parent page. |
paul@347 | 158 | |
paul@347 | 159 | The optional 'descriptions' indicates the nature of any description given |
paul@347 | 160 | for events in the output resource. |
paul@347 | 161 | |
paul@347 | 162 | If the 'write' parameter is specified, use it to write output. |
paul@347 | 163 | """ |
paul@347 | 164 | |
paul@347 | 165 | write = write or request.write |
paul@347 | 166 | event_details = event.getDetails() |
paul@347 | 167 | event_metadata = event.getMetadata() |
paul@347 | 168 | |
paul@347 | 169 | if mimetype == "text/calendar" and vCalendar is not None: |
paul@347 | 170 | |
paul@347 | 171 | # NOTE: A custom formatter making attributes for links and plain |
paul@347 | 172 | # NOTE: text for values could be employed here. |
paul@347 | 173 | |
paul@364 | 174 | _write = vCalendar.iterwrite(write=write).write |
paul@347 | 175 | |
paul@347 | 176 | # Get the summary details. |
paul@347 | 177 | |
paul@347 | 178 | event_summary = event.getSummary(parent) |
paul@347 | 179 | link = event.getEventURL() |
paul@347 | 180 | |
paul@347 | 181 | # Output the event details. |
paul@347 | 182 | |
paul@364 | 183 | _write("BEGIN", {}, "VEVENT") |
paul@364 | 184 | _write("UID", {}, link) |
paul@364 | 185 | _write("URL", {}, link) |
paul@364 | 186 | _write("DTSTAMP", {}, "%04d%02d%02dT%02d%02d%02dZ" % event_metadata["created"].as_tuple()[:6]) |
paul@364 | 187 | _write("LAST-MODIFIED", {}, "%04d%02d%02dT%02d%02d%02dZ" % event_metadata["last-modified"].as_tuple()[:6]) |
paul@364 | 188 | _write("SEQUENCE", {}, "%d" % event_metadata["sequence"]) |
paul@347 | 189 | |
paul@347 | 190 | start = event_details["start"] |
paul@347 | 191 | end = event_details["end"] |
paul@347 | 192 | |
paul@347 | 193 | if isinstance(start, DateTime): |
paul@347 | 194 | params, value = getCalendarDateTime(start) |
paul@347 | 195 | else: |
paul@347 | 196 | params, value = {"VALUE" : "DATE"}, "%04d%02d%02d" % start.as_date().as_tuple() |
paul@364 | 197 | _write("DTSTART", params, value) |
paul@347 | 198 | |
paul@347 | 199 | if isinstance(end, DateTime): |
paul@347 | 200 | params, value = getCalendarDateTime(end) |
paul@347 | 201 | else: |
paul@347 | 202 | params, value = {"VALUE" : "DATE"}, "%04d%02d%02d" % end.next_day().as_date().as_tuple() |
paul@364 | 203 | _write("DTEND", params, value) |
paul@347 | 204 | |
paul@364 | 205 | _write("SUMMARY", {}, event_summary) |
paul@347 | 206 | |
paul@347 | 207 | # Optional details. |
paul@347 | 208 | |
paul@347 | 209 | if event_details.get("topics") or event_details.get("categories"): |
paul@364 | 210 | _write("CATEGORIES", {}, event_details.get("topics") or event_details.get("categories")) |
paul@347 | 211 | if event_details.has_key("location"): |
paul@364 | 212 | _write("LOCATION", {}, event_details["location"]) |
paul@347 | 213 | if event_details.has_key("geo"): |
paul@364 | 214 | _write("GEO", {}, tuple([str(ref.to_degrees()) for ref in event_details["geo"]])) |
paul@347 | 215 | |
paul@364 | 216 | _write("END", {}, "VEVENT") |
paul@347 | 217 | |
paul@347 | 218 | elif mimetype == "application/rss+xml": |
paul@347 | 219 | |
paul@347 | 220 | event_page = event.getPage() |
paul@347 | 221 | event_details = event.getDetails() |
paul@347 | 222 | |
paul@347 | 223 | # Get a parser and formatter for the formatting of some attributes. |
paul@347 | 224 | |
paul@347 | 225 | fmt = request.html_formatter |
paul@347 | 226 | |
paul@347 | 227 | # Get the summary details. |
paul@347 | 228 | |
paul@347 | 229 | event_summary = event.getSummary(parent) |
paul@347 | 230 | link = event.getEventURL() |
paul@347 | 231 | |
paul@365 | 232 | write('<item>\n') |
paul@365 | 233 | write('<title>%s</title>\n' % escape(event_summary)) |
paul@365 | 234 | write('<link>%s</link>\n' % link) |
paul@347 | 235 | |
paul@347 | 236 | # Write a description according to the preferred source of |
paul@347 | 237 | # descriptions. |
paul@347 | 238 | |
paul@347 | 239 | if descriptions == "page": |
paul@347 | 240 | description = event_details.get("description", "") |
paul@347 | 241 | else: |
paul@347 | 242 | description = event_metadata["last-comment"] |
paul@347 | 243 | |
paul@365 | 244 | write('<description>%s</description>\n' % |
paul@347 | 245 | fmt.text(event_page.formatText(description, fmt))) |
paul@347 | 246 | |
paul@347 | 247 | for topic in event_details.get("topics") or event_details.get("categories") or []: |
paul@365 | 248 | write('<category>%s</category>\n' % |
paul@347 | 249 | fmt.text(event_page.formatText(topic, fmt))) |
paul@347 | 250 | |
paul@365 | 251 | write('<pubDate>%s</pubDate>\n' % event_metadata["created"].as_HTTP_datetime_string()) |
paul@365 | 252 | write('<guid>%s#%s</guid>\n' % (link, event_metadata["sequence"])) |
paul@365 | 253 | write('</item>\n') |
paul@347 | 254 | |
paul@347 | 255 | elif mimetype == "text/html": |
paul@347 | 256 | fmt = request.html_formatter |
paul@347 | 257 | fmt.setPage(request.page) |
paul@347 | 258 | formatEvent(event, request, fmt, write=write) |
paul@347 | 259 | |
paul@347 | 260 | # iCalendar format helper functions. |
paul@347 | 261 | |
paul@347 | 262 | def getCalendarDateTime(datetime): |
paul@347 | 263 | |
paul@347 | 264 | """ |
paul@347 | 265 | Write to the given 'request' the 'datetime' using appropriate time zone |
paul@347 | 266 | information. |
paul@347 | 267 | """ |
paul@347 | 268 | |
paul@347 | 269 | utc_datetime = datetime.to_utc() |
paul@347 | 270 | if utc_datetime: |
paul@347 | 271 | return {"VALUE" : "DATE-TIME"}, "%04d%02d%02dT%02d%02d%02dZ" % utc_datetime.padded().as_tuple()[:-1] |
paul@347 | 272 | else: |
paul@347 | 273 | zone = datetime.time_zone() |
paul@347 | 274 | params = {"VALUE" : "DATE-TIME"} |
paul@347 | 275 | if zone: |
paul@347 | 276 | params["TZID"] = zone |
paul@347 | 277 | return params, "%04d%02d%02dT%02d%02d%02d" % datetime.padded().as_tuple()[:-1] |
paul@347 | 278 | |
paul@347 | 279 | # Helper functions. |
paul@347 | 280 | |
paul@347 | 281 | def getOrderedEvents(events): |
paul@347 | 282 | |
paul@347 | 283 | """ |
paul@347 | 284 | Return a list with the given 'events' ordered according to their start and |
paul@347 | 285 | end dates. |
paul@347 | 286 | """ |
paul@347 | 287 | |
paul@347 | 288 | ordered_events = events[:] |
paul@347 | 289 | ordered_events.sort() |
paul@347 | 290 | return ordered_events |
paul@347 | 291 | |
paul@347 | 292 | # vim: tabstop=4 expandtab shiftwidth=4 |