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 functions. 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 start=current-N shows event details relative to the current month 56 end=YYYY-MM shows event details ending at the specified month 57 end=current+N shows event details relative to the current month 58 59 mode=calendar shows a calendar view of events 60 mode=list shows a list of events by month 61 mode=ics provides iCalendar data for the events 62 63 names=daily shows the name of an event on every day of that event 64 names=weekly shows the name of an event once per week 65 66 calendar=NAME uses the given NAME to provide request parameters which 67 can be used to control the calendar view 68 """ 69 70 request = macro.request 71 fmt = macro.formatter 72 page = fmt.page 73 _ = request.getText 74 75 # Interpret the arguments. 76 77 try: 78 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 79 except AttributeError: 80 parsed_args = args.split(",") 81 82 parsed_args = [arg for arg in parsed_args if arg] 83 84 # Get special arguments. 85 86 category_names = [] 87 calendar_start = None 88 calendar_end = None 89 mode = "calendar" 90 name_usage = "daily" 91 calendar_name = None 92 93 for arg in parsed_args: 94 if arg.startswith("start="): 95 calendar_start = EventAggregatorSupport.getParameterMonth(arg[6:]) 96 97 elif arg.startswith("end="): 98 calendar_end = EventAggregatorSupport.getParameterMonth(arg[4:]) 99 100 elif arg.startswith("mode="): 101 mode = arg[5:] 102 103 elif arg.startswith("names="): 104 name_usage = arg[6:] 105 106 elif arg.startswith("calendar="): 107 calendar_name = arg[9:] 108 109 else: 110 category_names.append(arg) 111 112 # Find request parameters to override settings. 113 114 if calendar_name is not None: 115 calendar_start = EventAggregatorSupport.getFormMonth(request, calendar_name, "start") or calendar_start 116 calendar_end = EventAggregatorSupport.getFormMonth(request, calendar_name, "end") or calendar_end 117 118 # Get the events. 119 120 events, shown_events, all_shown_events, earliest, latest = \ 121 EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end) 122 123 # Get a concrete period of time. 124 125 first, last = EventAggregatorSupport.getConcretePeriod(calendar_start, calendar_end, earliest, latest) 126 127 # Define some useful navigation months. 128 129 if calendar_name is not None: 130 span = EventAggregatorSupport.span(first, last) 131 number_of_months = span[0] * 12 + span[1] + 1 132 133 previous_month_start = EventAggregatorSupport.prevmonth(first) 134 next_month_start = EventAggregatorSupport.nextmonth(first) 135 previous_month_end = EventAggregatorSupport.prevmonth(last) 136 next_month_end = EventAggregatorSupport.nextmonth(last) 137 138 previous_set_start = EventAggregatorSupport.monthupdate(first, -number_of_months) 139 next_set_start = EventAggregatorSupport.monthupdate(first, number_of_months) 140 previous_set_end = EventAggregatorSupport.monthupdate(last, -number_of_months) 141 next_set_end = EventAggregatorSupport.monthupdate(last, number_of_months) 142 143 # Make a calendar. 144 145 output = [] 146 147 # Output top-level information. 148 149 if mode == "list": 150 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 151 152 # Visit all months in the requested range, or across known events. 153 154 for year, month in EventAggregatorSupport.daterange(first, last): 155 156 # Either output a calendar view... 157 158 if mode == "calendar": 159 160 # Output a month. 161 162 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 163 164 output.append(fmt.table_row(on=1)) 165 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "7"})) 166 167 # Either write a month heading or produce a link for navigable 168 # calendars. 169 170 month_label = _(month_labels[month - 1]) # zero-based labels 171 172 if calendar_name is not None: 173 174 # Links to the previous set of months and to a calendar shifted 175 # back one month. 176 177 previous_set_link = "%s-start=%04d-%02d&%s-end=%04d-%02d" % ( 178 (calendar_name,) + previous_set_start + (calendar_name,) + previous_set_end 179 ) 180 previous_month_link = "%s-start=%04d-%02d&%s-end=%04d-%02d" % ( 181 (calendar_name,) + previous_month_start + (calendar_name,) + previous_month_end 182 ) 183 184 output.append(fmt.span(on=1, css_class="previous-month")) 185 output.append(page.link_to_raw(request, wikiutil.escape("<<"), previous_set_link)) 186 output.append(fmt.text(" ")) 187 output.append(page.link_to_raw(request, wikiutil.escape("<"), previous_month_link)) 188 output.append(fmt.span(on=0)) 189 190 # Links to the next set of months and to a calendar shifted 191 # forward one month. 192 193 next_set_link = "%s-start=%04d-%02d&%s-end=%04d-%02d" % ( 194 (calendar_name,) + next_set_start + (calendar_name,) + next_set_end 195 ) 196 next_month_link = "%s-start=%04d-%02d&%s-end=%04d-%02d" % ( 197 (calendar_name,) + next_month_start + (calendar_name,) + next_month_end 198 ) 199 200 output.append(fmt.span(on=1, css_class="next-month")) 201 output.append(page.link_to_raw(request, wikiutil.escape(">"), next_month_link)) 202 output.append(fmt.text(" ")) 203 output.append(page.link_to_raw(request, wikiutil.escape(">>"), next_set_link)) 204 output.append(fmt.span(on=0)) 205 206 # A link leading to this month being at the top of the calendar. 207 208 full_month_label = "%s %s" % (month_label, year) 209 month_link = "%s-start=%04d-%02d&%s-end=%04d-%02d" % ( 210 (calendar_name, year, month, calendar_name) + 211 EventAggregatorSupport.monthupdate((year, month), number_of_months - 1) 212 ) 213 output.append(page.link_to_raw(request, wikiutil.escape(full_month_label), month_link)) 214 215 else: 216 output.append(fmt.span(on=1)) 217 output.append(fmt.text(month_label)) 218 output.append(fmt.span(on=0)) 219 output.append(fmt.text(" ")) 220 output.append(fmt.span(on=1)) 221 output.append(fmt.text(unicode(year))) 222 output.append(fmt.span(on=0)) 223 224 output.append(fmt.table_cell(on=0)) 225 output.append(fmt.table_row(on=0)) 226 227 # Weekday headings. 228 229 output.append(fmt.table_row(on=1)) 230 231 for weekday in range(0, 7): 232 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) 233 output.append(fmt.text(_(weekday_labels[weekday]))) 234 output.append(fmt.table_cell(on=0)) 235 236 output.append(fmt.table_row(on=0)) 237 238 # Process the days of the month. 239 240 start_weekday, number_of_days = calendar.monthrange(year, month) 241 242 # The start weekday is the weekday of day number 1. 243 # Find the first day of the week, counting from below zero, if 244 # necessary, in order to land on the first day of the month as 245 # day number 1. 246 247 first_day = 1 - start_weekday 248 249 while first_day <= number_of_days: 250 251 # Find events in this week and determine how to mark them on the 252 # calendar. 253 254 week_start = (year, month, max(first_day, 1)) 255 week_end = (year, month, min(first_day + 6, number_of_days)) 256 257 week_coverage, week_events = EventAggregatorSupport.getCoverage( 258 week_start, week_end, shown_events.get((year, month), [])) 259 260 # Output a week, starting with the day numbers. 261 262 output.append(fmt.table_row(on=1)) 263 264 for weekday in range(0, 7): 265 day = first_day + weekday 266 date = (year, month, day) 267 268 # Output out-of-month days. 269 270 if day < 1 or day > number_of_days: 271 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-excluded"})) 272 output.append(fmt.table_cell(on=0)) 273 274 # Output normal days. 275 276 else: 277 if date in week_coverage: 278 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-busy"})) 279 else: 280 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-empty"})) 281 282 output.append(fmt.div(on=1)) 283 output.append(fmt.span(on=1, css_class="event-day-number")) 284 output.append(fmt.text(unicode(day))) 285 output.append(fmt.span(on=0)) 286 output.append(fmt.div(on=0)) 287 288 # End of day. 289 290 output.append(fmt.table_cell(on=0)) 291 292 # End of day numbers. 293 294 output.append(fmt.table_row(on=0)) 295 296 # Either generate empty days... 297 298 if not week_events: 299 output.append(fmt.table_row(on=1)) 300 301 for weekday in range(0, 7): 302 day = first_day + weekday 303 date = (year, month, day) 304 305 # Output out-of-month days. 306 307 if day < 1 or day > number_of_days: 308 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 309 output.append(fmt.table_cell(on=0)) 310 311 # Output empty days. 312 313 else: 314 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 315 316 output.append(fmt.table_row(on=0)) 317 318 # Or visit each set of scheduled events... 319 320 else: 321 for coverage, events in week_events: 322 323 # Output each set. 324 325 output.append(fmt.table_row(on=1)) 326 327 # Then, output day details. 328 329 for weekday in range(0, 7): 330 day = first_day + weekday 331 date = (year, month, day) 332 333 # Skip out-of-month days. 334 335 if day < 1 or day > number_of_days: 336 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 337 output.append(fmt.table_cell(on=0)) 338 continue 339 340 # Output the day. 341 342 if date in coverage: 343 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-busy"})) 344 else: 345 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 346 347 # Get event details for the current day. 348 349 for event_page, event_details in events: 350 if not (event_details["start"] <= date <= event_details["end"]): 351 continue 352 353 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details) 354 355 # Generate a colour for the event. 356 357 bg = getColour(event_page.page_name) 358 fg = getBlackOrWhite(bg) 359 style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)) 360 hidden_style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + bg)) 361 362 css_classes = ["event-summary"] 363 364 if event_details["start"] == date: 365 css_classes.append("event-starts") 366 start_of_event = 1 367 else: 368 start_of_event = 0 369 370 if event_details["end"] == date: 371 css_classes.append("event-ends") 372 373 # Output the event. 374 375 if name_usage == "daily" or start_of_event or weekday == 0 or day == 1: 376 hide_text = 0 377 else: 378 hide_text = 1 379 380 if not hide_text: 381 output.append(fmt.div(on=1, css_class=(" ".join(css_classes)), style=style)) 382 output.append(event_page.link_to_raw(request, wikiutil.escape(event_summary))) 383 else: 384 output.append(fmt.div(on=1, css_class=(" ".join(css_classes)), style=hidden_style)) 385 output.append(fmt.text(event_summary)) 386 387 output.append(fmt.div(on=0)) 388 389 # End of day. 390 391 output.append(fmt.table_cell(on=0)) 392 393 # End of set. 394 395 output.append(fmt.table_row(on=0)) 396 397 # Process the next week... 398 399 first_day += 7 400 401 # End of month. 402 403 output.append(fmt.table(on=0)) 404 405 # Or output a summary view... 406 407 elif mode == "list": 408 409 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-month"})) 410 output.append(fmt.div(on=1, attr={"class" : "event-listings-month-heading"})) 411 output.append(fmt.span(on=1)) 412 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 413 output.append(fmt.span(on=0)) 414 output.append(fmt.text(" ")) 415 output.append(fmt.span(on=1)) 416 output.append(fmt.text(unicode(year))) 417 output.append(fmt.span(on=0)) 418 output.append(fmt.div(on=0)) 419 420 output.append(fmt.bullet_list(on=1, attr={"class" : "event-month-listings"})) 421 422 for event_page, event_details in shown_events.get((year, month), []): 423 424 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details) 425 426 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 427 428 # Link to the page using the summary. 429 430 output.append(event_page.link_to_raw(request, wikiutil.escape(event_summary))) 431 432 # Add the event details. 433 434 output.append(fmt.definition_list(on=1, attr={"class" : "event-details"})) 435 436 for key, value in event_details.items(): 437 output.append(fmt.definition_term(on=1)) 438 output.append(fmt.text(key)) 439 output.append(fmt.definition_term(on=0)) 440 output.append(fmt.definition_desc(on=1)) 441 output.append(fmt.text(unicode(value))) 442 output.append(fmt.definition_desc(on=0)) 443 444 output.append(fmt.definition_list(on=0)) 445 output.append(fmt.listitem(on=0)) 446 447 output.append(fmt.bullet_list(on=0)) 448 449 # Output top-level information. 450 451 # End of list view output. 452 453 if mode == "list": 454 output.append(fmt.bullet_list(on=0)) 455 456 return ''.join(output) 457 458 # vim: tabstop=4 expandtab shiftwidth=4