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 execute(macro, args): 115 116 """ 117 Execute the 'macro' with the given 'args': an optional list of selected 118 category names (categories whose pages are to be shown), together with 119 optional named arguments of the form "start=YYYY-MM" and "end=YYYY-MM" 120 (indicating a restricted view of all events), and "mode=list" or 121 "mode=calendar" (indicating the style of view). 122 """ 123 124 request = macro.request 125 fmt = macro.formatter 126 page = fmt.page 127 _ = request.getText 128 129 # Interpret the arguments. 130 131 try: 132 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 133 except AttributeError: 134 parsed_args = args.split(",") 135 136 parsed_args = [arg for arg in parsed_args if arg] 137 138 # Get special arguments. 139 140 category_names = [] 141 calendar_start = None 142 calendar_end = None 143 mode = "calendar" 144 145 for arg in parsed_args: 146 if arg.startswith("start="): 147 calendar_start = getMonth(arg[6:]) 148 elif arg.startswith("end="): 149 calendar_end = getMonth(arg[4:]) 150 elif arg.startswith("mode="): 151 mode = arg[5:] 152 else: 153 category_names.append(arg) 154 155 # Generate a list of events found on pages belonging to the specified 156 # categories, as found in the macro arguments. 157 158 events = [] 159 shown_events = [] 160 earliest = calendar_start 161 latest = calendar_end 162 163 for category_name in category_names: 164 165 # Get the pages and page names in the category. 166 167 pages_in_category = getPages(category_name, request) 168 169 # Visit each page in the category. 170 171 for page_in_category in pages_in_category: 172 pagename = page_in_category.page_name 173 174 # Get a real page, not a result page. 175 176 real_page_in_category = Page(request, pagename) 177 event_details = getEventDetails(real_page_in_category) 178 event = (real_page_in_category, event_details) 179 events.append(event) 180 181 # Test for the suitability of the event. 182 183 if event_details.has_key("start") and event_details.has_key("end"): 184 185 start_month = event_details["start"][:2] 186 end_month = event_details["end"][:2] 187 188 # Compare the months of the dates to the requested calendar 189 # window, if any. 190 191 if (calendar_start is None or end_month >= calendar_start) and \ 192 (calendar_end is None or start_month <= calendar_end): 193 194 shown_events.append(event) 195 196 if earliest is None or start_month < earliest: 197 earliest = start_month 198 if latest is None or end_month > latest: 199 latest = end_month 200 201 # Make a calendar. 202 203 output = [] 204 205 if mode == "calendar": 206 207 end_year = latest[0] 208 209 for year in range(earliest[0], end_year + 1): 210 if year < latest[0]: 211 end_month = 12 212 else: 213 end_month = latest[1] 214 215 if year > earliest[0]: 216 start_month = 1 217 else: 218 start_month = earliest[1] 219 220 for month in range(start_month, end_month + 1): 221 222 # Output a month. 223 224 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 225 226 output.append(fmt.table_row(on=1)) 227 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "7"})) 228 output.append(fmt.span(on=1)) 229 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 230 output.append(fmt.span(on=0)) 231 output.append(fmt.text(" ")) 232 output.append(fmt.span(on=1)) 233 output.append(fmt.text(year)) 234 output.append(fmt.span(on=0)) 235 output.append(fmt.table_cell(on=0)) 236 output.append(fmt.table_row(on=0)) 237 238 # Weekday headings. 239 240 output.append(fmt.table_row(on=1)) 241 242 for weekday in range(0, 7): 243 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading"})) 244 output.append(fmt.text(_(weekday_labels[weekday]))) 245 output.append(fmt.table_cell(on=0)) 246 247 output.append(fmt.table_row(on=0)) 248 249 # Process the days of the month. 250 251 start_weekday, number_of_days = calendar.monthrange(year, month) 252 253 # The start weekday is the weekday of day number 1. 254 # Find the first day of the week, counting from below zero, if 255 # necessary, in order to land on the first day of the month as 256 # day number 1. 257 258 first_day = 1 - start_weekday 259 260 while first_day <= number_of_days: 261 262 # Output a week. 263 264 output.append(fmt.table_row(on=1)) 265 266 for weekday in range(0, 7): 267 day = first_day + weekday 268 269 # Output out-of-month days. 270 271 if day < 1 or day > number_of_days: 272 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 273 output.append(fmt.table_cell(on=0)) 274 275 # Output normal days. 276 277 else: 278 # Get event details. 279 # NOTE: Can be made more efficient. 280 281 date = (year, month, day) 282 day_events = [] 283 284 for event_page, event_details in shown_events: 285 286 # Test for the event on the current day. 287 288 if event_details["start"] <= date <= event_details["end"]: 289 day_events.append((event_page, event_details)) 290 291 # Output the day. 292 293 if day_events: 294 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-busy"})) 295 else: 296 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 297 298 output.append(fmt.div(on=1, css_class="event-day-number")) 299 output.append(fmt.text(day)) 300 output.append(fmt.div(on=0)) 301 302 # Show event details. 303 304 for event_page, event_details in day_events: 305 306 # Get a pretty version of the page name. 307 308 pretty_pagename = getPrettyPageName(event_page) 309 310 # Output the event. 311 312 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 313 output.append(fmt.linebreak()) 314 315 # End of day. 316 317 output.append(fmt.table_cell(on=0)) 318 319 output.append(fmt.table_row(on=0)) 320 321 first_day += 7 322 323 # End of month. 324 325 output.append(fmt.table(on=0)) 326 327 elif mode == "list": 328 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 329 330 for event_page, event_details in shown_events: 331 332 # Get a pretty version of the page name. 333 334 pretty_pagename = getPrettyPageName(event_page) 335 336 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 337 338 # Link to the page using the pretty name. 339 340 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 341 342 # Add the event details. 343 344 output.append(fmt.definition_list(on=1, attr={"class" : "event-details"})) 345 346 for key, value in event_details.items(): 347 output.append(fmt.definition_term(on=1)) 348 output.append(fmt.text(key)) 349 output.append(fmt.definition_term(on=0)) 350 output.append(fmt.definition_desc(on=1)) 351 output.append(fmt.text(value)) 352 output.append(fmt.definition_desc(on=0)) 353 354 output.append(fmt.definition_list(on=0)) 355 output.append(fmt.listitem(on=0)) 356 357 output.append(fmt.bullet_list(on=0)) 358 359 return ''.join(output) 360 361 # vim: tabstop=4 expandtab shiftwidth=4