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