1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator Macro 4 5 @copyright: 2008, 2009 by Paul Boddie <paul@boddie.org.uk> 6 @copyright: 2000-2004 Juergen Hermann <jh@web.de>, 7 2005-2008 MoinMoin:ThomasWaldmann. 8 @license: GNU GPL (v2 or later), see COPYING.txt for details. 9 """ 10 11 from MoinMoin import wikiutil 12 import EventAggregatorSupport 13 import calendar 14 15 try: 16 set 17 except NameError: 18 from sets import Set as set 19 20 Dependencies = ['pages'] 21 22 # Date labels. 23 24 month_labels = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] 25 weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 26 27 # HTML-related functions. 28 29 def getColour(s): 30 colour = [0, 0, 0] 31 digit = 0 32 for c in s: 33 colour[digit] += ord(c) 34 colour[digit] = colour[digit] % 256 35 digit += 1 36 digit = digit % 3 37 return tuple(colour) 38 39 def getBlackOrWhite(colour): 40 if sum(colour) / 3.0 > 127: 41 return (0, 0, 0) 42 else: 43 return (255, 255, 255) 44 45 # Macro function. 46 47 def execute(macro, args): 48 49 """ 50 Execute the 'macro' with the given 'args': an optional list of selected 51 category names (categories whose pages are to be shown), together with 52 optional named arguments of the following forms: 53 54 start=YYYY-MM shows event details starting from the specified month 55 end=YYYY-MM shows event details ending at the specified month 56 57 mode=calendar shows a calendar view of events 58 mode=list shows a list of events by month 59 mode=ics provides iCalendar data for the events 60 61 names=daily shows the name of an event on every day of that event 62 names=weekly shows the name of an event once per week 63 """ 64 65 request = macro.request 66 fmt = macro.formatter 67 page = fmt.page 68 _ = request.getText 69 70 # Interpret the arguments. 71 72 try: 73 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 74 except AttributeError: 75 parsed_args = args.split(",") 76 77 parsed_args = [arg for arg in parsed_args if arg] 78 79 # Get special arguments. 80 81 category_names = [] 82 calendar_start = None 83 calendar_end = None 84 mode = "calendar" 85 name_usage = "daily" 86 87 for arg in parsed_args: 88 if arg.startswith("start="): 89 calendar_start = EventAggregatorSupport.getMonth(arg[6:]) 90 elif arg.startswith("end="): 91 calendar_end = EventAggregatorSupport.getMonth(arg[4:]) 92 elif arg.startswith("mode="): 93 mode = arg[5:] 94 elif arg.startswith("names="): 95 name_usage = arg[6:] 96 else: 97 category_names.append(arg) 98 99 events, shown_events, all_shown_events, earliest, latest = \ 100 EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end) 101 102 # Make a calendar. 103 104 output = [] 105 106 # Output top-level information. 107 108 if mode == "list": 109 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 110 111 # Visit all months in the requested range, or across known events. 112 113 first = calendar_start or earliest 114 last = calendar_end or latest 115 116 for year, month in EventAggregatorSupport.daterange(first, last): 117 118 # Either output a calendar view... 119 120 if mode == "calendar": 121 122 # Output a month. 123 124 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 125 126 output.append(fmt.table_row(on=1)) 127 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "7"})) 128 output.append(fmt.span(on=1)) 129 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 130 output.append(fmt.span(on=0)) 131 output.append(fmt.text(" ")) 132 output.append(fmt.span(on=1)) 133 output.append(fmt.text(year)) 134 output.append(fmt.span(on=0)) 135 output.append(fmt.table_cell(on=0)) 136 output.append(fmt.table_row(on=0)) 137 138 # Weekday headings. 139 140 output.append(fmt.table_row(on=1)) 141 142 for weekday in range(0, 7): 143 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) 144 output.append(fmt.text(_(weekday_labels[weekday]))) 145 output.append(fmt.table_cell(on=0)) 146 147 output.append(fmt.table_row(on=0)) 148 149 # Process the days of the month. 150 151 start_weekday, number_of_days = calendar.monthrange(year, month) 152 153 # The start weekday is the weekday of day number 1. 154 # Find the first day of the week, counting from below zero, if 155 # necessary, in order to land on the first day of the month as 156 # day number 1. 157 158 first_day = 1 - start_weekday 159 160 while first_day <= number_of_days: 161 162 # Find events in this week and determine how to mark them on the 163 # calendar. 164 165 week_events = [] 166 week_coverage = set() 167 168 week_start = (year, month, max(first_day, 1)) 169 week_end = (year, month, min(first_day + 6, number_of_days)) 170 171 # Get event details. 172 173 for event in shown_events.get((year, month), []): 174 event_page, event_details = event 175 176 # Test for the event in the current week. 177 178 if event_details["start"] <= week_end and event_details["end"] >= week_start: 179 180 # Find the coverage of this week for the event. 181 182 event_start = max(event_details["start"], week_start) 183 event_end = min(event_details["end"], week_end) 184 event_coverage = set(EventAggregatorSupport.daterange(event_start, event_end)) 185 186 # Update the overall coverage. 187 188 week_coverage.update(event_coverage) 189 190 # Try and fit the event into the events list. 191 192 for i, (coverage, events) in enumerate(week_events): 193 194 # Where the event does not overlap with the current 195 # element, add it alongside existing events. 196 197 if not coverage.intersection(event_coverage): 198 events.append(event) 199 week_events[i] = coverage.union(event_coverage), events 200 break 201 202 # Make a new element in the list if the event cannot be 203 # marked alongside existing events. 204 205 else: 206 week_events.append((event_coverage, [event])) 207 208 # Output a week, starting with the day numbers. 209 210 output.append(fmt.table_row(on=1)) 211 212 for weekday in range(0, 7): 213 day = first_day + weekday 214 date = (year, month, day) 215 216 # Output out-of-month days. 217 218 if day < 1 or day > number_of_days: 219 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-excluded"})) 220 output.append(fmt.table_cell(on=0)) 221 222 # Output normal days. 223 224 else: 225 if date in week_coverage: 226 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-busy"})) 227 else: 228 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-empty"})) 229 230 output.append(fmt.div(on=1)) 231 output.append(fmt.span(on=1, css_class="event-day-number")) 232 output.append(fmt.text(day)) 233 output.append(fmt.span(on=0)) 234 output.append(fmt.div(on=0)) 235 236 # End of day. 237 238 output.append(fmt.table_cell(on=0)) 239 240 # End of day numbers. 241 242 output.append(fmt.table_row(on=0)) 243 244 # Either generate empty days... 245 246 if not week_events: 247 output.append(fmt.table_row(on=1)) 248 249 for weekday in range(0, 7): 250 day = first_day + weekday 251 date = (year, month, day) 252 253 # Output out-of-month days. 254 255 if day < 1 or day > number_of_days: 256 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 257 output.append(fmt.table_cell(on=0)) 258 259 # Output empty days. 260 261 else: 262 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 263 264 output.append(fmt.table_row(on=0)) 265 266 # Or visit each set of scheduled events... 267 268 else: 269 for coverage, events in week_events: 270 271 # Output each set. 272 273 output.append(fmt.table_row(on=1)) 274 275 # Then, output day details. 276 277 for weekday in range(0, 7): 278 day = first_day + weekday 279 date = (year, month, day) 280 281 # Skip out-of-month days. 282 283 if day < 1 or day > number_of_days: 284 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 285 output.append(fmt.table_cell(on=0)) 286 continue 287 288 # Output the day. 289 290 if date in coverage: 291 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-busy"})) 292 else: 293 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 294 295 # Get event details for the current day. 296 297 for event_page, event_details in events: 298 if not (event_details["start"] <= date <= event_details["end"]): 299 continue 300 301 # Get a pretty version of the page name. 302 303 pretty_pagename = EventAggregatorSupport.getPrettyPageName(event_page) 304 305 # Generate a colour for the event. 306 307 bg = getColour(event_page.page_name) 308 fg = getBlackOrWhite(bg) 309 310 css_classes = ["event-summary"] 311 312 if event_details["start"] == date: 313 css_classes.append("event-starts") 314 start_of_event = 1 315 else: 316 start_of_event = 0 317 318 if event_details["end"] == date: 319 css_classes.append("event-ends") 320 321 # Output the event. 322 323 if name_usage == "daily" or start_of_event or weekday == 0 or day == 1: 324 hide_text = 0 325 else: 326 hide_text = 1 327 328 output.append(fmt.div(on=1, css_class=(" ".join(css_classes)), 329 style=("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)))) 330 331 if not hide_text: 332 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 333 334 output.append(fmt.div(on=0)) 335 336 # End of day. 337 338 output.append(fmt.table_cell(on=0)) 339 340 # End of set. 341 342 output.append(fmt.table_row(on=0)) 343 344 # Process the next week... 345 346 first_day += 7 347 348 # End of month. 349 350 output.append(fmt.table(on=0)) 351 352 # Or output a summary view... 353 354 elif mode == "list": 355 356 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-month"})) 357 output.append(fmt.div(on=1, attr={"class" : "event-listings-month-heading"})) 358 output.append(fmt.span(on=1)) 359 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 360 output.append(fmt.span(on=0)) 361 output.append(fmt.text(" ")) 362 output.append(fmt.span(on=1)) 363 output.append(fmt.text(year)) 364 output.append(fmt.span(on=0)) 365 output.append(fmt.div(on=0)) 366 367 output.append(fmt.bullet_list(on=1, attr={"class" : "event-month-listings"})) 368 369 for event_page, event_details in shown_events.get((year, month), []): 370 371 # Get a pretty version of the page name. 372 373 pretty_pagename = EventAggregatorSupport.getPrettyPageName(event_page) 374 375 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 376 377 # Link to the page using the pretty name. 378 379 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 380 381 # Add the event details. 382 383 output.append(fmt.definition_list(on=1, attr={"class" : "event-details"})) 384 385 for key, value in event_details.items(): 386 output.append(fmt.definition_term(on=1)) 387 output.append(fmt.text(key)) 388 output.append(fmt.definition_term(on=0)) 389 output.append(fmt.definition_desc(on=1)) 390 output.append(fmt.text(value)) 391 output.append(fmt.definition_desc(on=0)) 392 393 output.append(fmt.definition_list(on=0)) 394 output.append(fmt.listitem(on=0)) 395 396 output.append(fmt.bullet_list(on=0)) 397 398 # Output top-level information. 399 400 # End of list view output. 401 402 if mode == "list": 403 output.append(fmt.bullet_list(on=0)) 404 405 return ''.join(output) 406 407 # vim: tabstop=4 expandtab shiftwidth=4