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.Page import Page 12 from MoinMoin import wikiutil, search, version 13 import calendar 14 import re 15 16 __version__ = "0.1" 17 18 Dependencies = ['pages'] 19 20 # Regular expressions where MoinMoin does not provide the required support. 21 22 category_regexp = None 23 definition_list_regexp = re.compile(ur'^\s+(?P<term>.*?)::\s(?P<desc>.*?)$', re.UNICODE | re.MULTILINE) 24 date_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})', re.UNICODE) 25 month_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})', re.UNICODE) 26 27 # Date labels. 28 29 month_labels = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] 30 weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 31 32 # Utility functions. 33 34 def isMoin15(): 35 return version.release.startswith("1.5.") 36 37 def getCategoryPattern(request): 38 global category_regexp 39 40 try: 41 return request.cfg.cache.page_category_regexact 42 except AttributeError: 43 44 # Use regular expression from MoinMoin 1.7.1 otherwise. 45 46 if category_regexp is None: 47 category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE) 48 return category_regexp 49 50 # The main activity functions. 51 52 def getPages(pagename, request): 53 54 "Return the links minus category links for 'pagename' using the 'request'." 55 56 query = search.QueryParser().parse_query('category:%s' % pagename) 57 if isMoin15(): 58 results = search.searchPages(request, query) 59 results.sortByPagename() 60 else: 61 results = search.searchPages(request, query, "page_name") 62 63 cat_pattern = getCategoryPattern(request) 64 pages = [] 65 for page in results.hits: 66 if not cat_pattern.match(page.page_name): 67 pages.append(page) 68 return pages 69 70 def getPrettyPageName(page): 71 72 "Return a nicely formatted title/name for the given 'page'." 73 74 return page.split_title(force=1).replace("_", " ").replace("/", u" ? ") 75 76 def getEventDetails(page): 77 78 "Return a dictionary of event details from the given 'page'." 79 80 event_details = {} 81 82 if page.pi["format"] == "wiki": 83 for match in definition_list_regexp.finditer(page.body): 84 # Permit case-insensitive list terms. 85 term = match.group("term").lower() 86 desc = match.group("desc") 87 if term in ("start", "end"): 88 desc = getDate(desc) 89 if desc is not None: 90 event_details[term] = desc 91 92 return event_details 93 94 def getDate(s): 95 96 "Parse the string 's', extracting and returning a date string." 97 98 m = date_regexp.search(s) 99 if m: 100 return tuple(map(int, m.groups())) 101 else: 102 return None 103 104 def getMonth(s): 105 106 "Parse the string 's', extracting and returning a month string." 107 108 m = month_regexp.search(s) 109 if m: 110 return tuple(map(int, m.groups())) 111 else: 112 return None 113 114 def monthrange(first, last): 115 results = [] 116 117 end_year = last[0] 118 119 for year in range(first[0], end_year + 1): 120 if year < last[0]: 121 end_month = 12 122 else: 123 end_month = last[1] 124 125 if year > first[0]: 126 start_month = 1 127 else: 128 start_month = first[1] 129 130 for month in range(start_month, end_month + 1): 131 results.append((year, month)) 132 133 return results 134 135 def getColour(s): 136 colour = [0, 0, 0] 137 digit = 0 138 for c in s: 139 colour[digit] += ord(c) 140 colour[digit] = colour[digit] % 256 141 digit += 1 142 digit = digit % 3 143 return tuple(colour) 144 145 def getBlackOrWhite(colour): 146 if sum(colour) / 3.0 > 127: 147 return (0, 0, 0) 148 else: 149 return (255, 255, 255) 150 151 def execute(macro, args): 152 153 """ 154 Execute the 'macro' with the given 'args': an optional list of selected 155 category names (categories whose pages are to be shown), together with 156 optional named arguments of the form "start=YYYY-MM" and "end=YYYY-MM" 157 (indicating a restricted view of all events), and "mode=list" or 158 "mode=calendar" (indicating the style of view). 159 """ 160 161 request = macro.request 162 fmt = macro.formatter 163 page = fmt.page 164 _ = request.getText 165 166 # Interpret the arguments. 167 168 try: 169 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 170 except AttributeError: 171 parsed_args = args.split(",") 172 173 parsed_args = [arg for arg in parsed_args if arg] 174 175 # Get special arguments. 176 177 category_names = [] 178 calendar_start = None 179 calendar_end = None 180 mode = "calendar" 181 182 for arg in parsed_args: 183 if arg.startswith("start="): 184 calendar_start = getMonth(arg[6:]) 185 elif arg.startswith("end="): 186 calendar_end = getMonth(arg[4:]) 187 elif arg.startswith("mode="): 188 mode = arg[5:] 189 else: 190 category_names.append(arg) 191 192 # Generate a list of events found on pages belonging to the specified 193 # categories, as found in the macro arguments. 194 195 events = [] 196 shown_events = {} 197 198 earliest = None 199 latest = None 200 201 for category_name in category_names: 202 203 # Get the pages and page names in the category. 204 205 pages_in_category = getPages(category_name, request) 206 207 # Visit each page in the category. 208 209 for page_in_category in pages_in_category: 210 pagename = page_in_category.page_name 211 212 # Get a real page, not a result page. 213 214 real_page_in_category = Page(request, pagename) 215 event_details = getEventDetails(real_page_in_category) 216 event = (real_page_in_category, event_details) 217 events.append(event) 218 219 # Test for the suitability of the event. 220 221 if event_details.has_key("start") and event_details.has_key("end"): 222 223 start_month = event_details["start"][:2] 224 end_month = event_details["end"][:2] 225 226 # Compare the months of the dates to the requested calendar 227 # window, if any. 228 229 if (calendar_start is None or end_month >= calendar_start) and \ 230 (calendar_end is None or start_month <= calendar_end): 231 232 if earliest is None or start_month < earliest: 233 earliest = start_month 234 if latest is None or end_month > latest: 235 latest = end_month 236 237 # Store the event in the month-specific dictionary. 238 239 first = max(start_month, calendar_start or start_month) 240 last = min(end_month, calendar_end or end_month) 241 242 for event_month in monthrange(first, last): 243 if not shown_events.has_key(event_month): 244 shown_events[event_month] = [] 245 shown_events[event_month].append(event) 246 247 # Make a calendar. 248 249 output = [] 250 251 if mode == "list": 252 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 253 254 # Visit all months in the requested range, or across known events. 255 256 first = calendar_start or earliest 257 last = calendar_end or latest 258 259 for year, month in monthrange(first, last): 260 261 # Either output a calendar view... 262 263 if mode == "calendar": 264 265 # Output a month. 266 267 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 268 269 output.append(fmt.table_row(on=1)) 270 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "7"})) 271 output.append(fmt.span(on=1)) 272 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 273 output.append(fmt.span(on=0)) 274 output.append(fmt.text(" ")) 275 output.append(fmt.span(on=1)) 276 output.append(fmt.text(year)) 277 output.append(fmt.span(on=0)) 278 output.append(fmt.table_cell(on=0)) 279 output.append(fmt.table_row(on=0)) 280 281 # Weekday headings. 282 283 output.append(fmt.table_row(on=1)) 284 285 for weekday in range(0, 7): 286 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading"})) 287 output.append(fmt.text(_(weekday_labels[weekday]))) 288 output.append(fmt.table_cell(on=0)) 289 290 output.append(fmt.table_row(on=0)) 291 292 # Process the days of the month. 293 294 start_weekday, number_of_days = calendar.monthrange(year, month) 295 296 # The start weekday is the weekday of day number 1. 297 # Find the first day of the week, counting from below zero, if 298 # necessary, in order to land on the first day of the month as 299 # day number 1. 300 301 first_day = 1 - start_weekday 302 303 while first_day <= number_of_days: 304 305 # Output a week. 306 307 output.append(fmt.table_row(on=1)) 308 309 for weekday in range(0, 7): 310 day = first_day + weekday 311 312 # Output out-of-month days. 313 314 if day < 1 or day > number_of_days: 315 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 316 output.append(fmt.table_cell(on=0)) 317 318 # Output normal days. 319 320 else: 321 # Get event details. 322 # NOTE: Can be made more efficient. 323 324 date = (year, month, day) 325 day_events = [] 326 327 for event_page, event_details in shown_events.get((year, month), []): 328 329 # Test for the event on the current day. 330 331 if event_details["start"] <= date <= event_details["end"]: 332 day_events.append((event_page, event_details)) 333 334 # Output the day. 335 336 if day_events: 337 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-busy"})) 338 else: 339 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 340 341 output.append(fmt.div(on=1)) 342 output.append(fmt.span(on=1, css_class="event-day-number")) 343 output.append(fmt.text(day)) 344 output.append(fmt.span(on=0)) 345 output.append(fmt.div(on=0)) 346 347 output.append(fmt.div(on=1, css_class="event-summaries")) 348 349 # Show event details. 350 351 for event_page, event_details in day_events: 352 353 # Get a pretty version of the page name. 354 355 pretty_pagename = getPrettyPageName(event_page) 356 357 # Generate a colour for the event. 358 359 bg = getColour(event_page.page_name) 360 fg = getBlackOrWhite(bg) 361 362 css_classes = ["event-summary"] 363 364 if event_details["start"] == date: 365 css_classes.append("event-starts") 366 367 if event_details["end"] == date: 368 css_classes.append("event-ends") 369 370 # Output the event. 371 372 output.append(fmt.div(on=1, css_class=(" ".join(css_classes)), 373 style=("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)))) 374 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 375 output.append(fmt.div(on=0)) 376 377 output.append(fmt.div(on=0)) 378 379 # End of day. 380 381 output.append(fmt.table_cell(on=0)) 382 383 output.append(fmt.table_row(on=0)) 384 385 first_day += 7 386 387 # End of month. 388 389 output.append(fmt.table(on=0)) 390 391 # Or output a summary view... 392 393 elif mode == "list": 394 395 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-month"})) 396 output.append(fmt.div(on=1, attr={"class" : "event-listings-month-heading"})) 397 output.append(fmt.span(on=1)) 398 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 399 output.append(fmt.span(on=0)) 400 output.append(fmt.text(" ")) 401 output.append(fmt.span(on=1)) 402 output.append(fmt.text(year)) 403 output.append(fmt.span(on=0)) 404 output.append(fmt.div(on=0)) 405 406 output.append(fmt.bullet_list(on=1, attr={"class" : "event-month-listings"})) 407 408 for event_page, event_details in shown_events.get((year, month), []): 409 410 # Get a pretty version of the page name. 411 412 pretty_pagename = getPrettyPageName(event_page) 413 414 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 415 416 # Link to the page using the pretty name. 417 418 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 419 420 # Add the event details. 421 422 output.append(fmt.definition_list(on=1, attr={"class" : "event-details"})) 423 424 for key, value in event_details.items(): 425 output.append(fmt.definition_term(on=1)) 426 output.append(fmt.text(key)) 427 output.append(fmt.definition_term(on=0)) 428 output.append(fmt.definition_desc(on=1)) 429 output.append(fmt.text(value)) 430 output.append(fmt.definition_desc(on=0)) 431 432 output.append(fmt.definition_list(on=0)) 433 output.append(fmt.listitem(on=0)) 434 435 output.append(fmt.bullet_list(on=0)) 436 437 if mode == "list": 438 output.append(fmt.bullet_list(on=0)) 439 440 return ''.join(output) 441 442 # vim: tabstop=4 expandtab shiftwidth=4