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 = None 161 latest = None 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 first = calendar_start or earliest 208 last = calendar_end or latest 209 210 end_year = last[0] 211 212 for year in range(first[0], end_year + 1): 213 if year < last[0]: 214 end_month = 12 215 else: 216 end_month = last[1] 217 218 if year > first[0]: 219 start_month = 1 220 else: 221 start_month = first[1] 222 223 for month in range(start_month, end_month + 1): 224 225 # Output a month. 226 227 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 228 229 output.append(fmt.table_row(on=1)) 230 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "7"})) 231 output.append(fmt.span(on=1)) 232 output.append(fmt.text(_(month_labels[month - 1]))) # zero-based labels 233 output.append(fmt.span(on=0)) 234 output.append(fmt.text(" ")) 235 output.append(fmt.span(on=1)) 236 output.append(fmt.text(year)) 237 output.append(fmt.span(on=0)) 238 output.append(fmt.table_cell(on=0)) 239 output.append(fmt.table_row(on=0)) 240 241 # Weekday headings. 242 243 output.append(fmt.table_row(on=1)) 244 245 for weekday in range(0, 7): 246 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading"})) 247 output.append(fmt.text(_(weekday_labels[weekday]))) 248 output.append(fmt.table_cell(on=0)) 249 250 output.append(fmt.table_row(on=0)) 251 252 # Process the days of the month. 253 254 start_weekday, number_of_days = calendar.monthrange(year, month) 255 256 # The start weekday is the weekday of day number 1. 257 # Find the first day of the week, counting from below zero, if 258 # necessary, in order to land on the first day of the month as 259 # day number 1. 260 261 first_day = 1 - start_weekday 262 263 while first_day <= number_of_days: 264 265 # Output a week. 266 267 output.append(fmt.table_row(on=1)) 268 269 for weekday in range(0, 7): 270 day = first_day + weekday 271 272 # Output out-of-month days. 273 274 if day < 1 or day > number_of_days: 275 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 276 output.append(fmt.table_cell(on=0)) 277 278 # Output normal days. 279 280 else: 281 # Get event details. 282 # NOTE: Can be made more efficient. 283 284 date = (year, month, day) 285 day_events = [] 286 287 for event_page, event_details in shown_events: 288 289 # Test for the event on the current day. 290 291 if event_details["start"] <= date <= event_details["end"]: 292 day_events.append((event_page, event_details)) 293 294 # Output the day. 295 296 if day_events: 297 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-busy"})) 298 else: 299 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 300 301 output.append(fmt.div(on=1, css_class="event-day-number")) 302 output.append(fmt.text(day)) 303 output.append(fmt.div(on=0)) 304 305 # Show event details. 306 307 for event_page, event_details in day_events: 308 309 # Get a pretty version of the page name. 310 311 pretty_pagename = getPrettyPageName(event_page) 312 313 # Output the event. 314 315 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 316 output.append(fmt.linebreak()) 317 318 # End of day. 319 320 output.append(fmt.table_cell(on=0)) 321 322 output.append(fmt.table_row(on=0)) 323 324 first_day += 7 325 326 # End of month. 327 328 output.append(fmt.table(on=0)) 329 330 elif mode == "list": 331 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 332 333 for event_page, event_details in shown_events: 334 335 # Get a pretty version of the page name. 336 337 pretty_pagename = getPrettyPageName(event_page) 338 339 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 340 341 # Link to the page using the pretty name. 342 343 output.append(event_page.link_to_raw(request, wikiutil.escape(pretty_pagename))) 344 345 # Add the event details. 346 347 output.append(fmt.definition_list(on=1, attr={"class" : "event-details"})) 348 349 for key, value in event_details.items(): 350 output.append(fmt.definition_term(on=1)) 351 output.append(fmt.text(key)) 352 output.append(fmt.definition_term(on=0)) 353 output.append(fmt.definition_desc(on=1)) 354 output.append(fmt.text(value)) 355 output.append(fmt.definition_desc(on=0)) 356 357 output.append(fmt.definition_list(on=0)) 358 output.append(fmt.listitem(on=0)) 359 360 output.append(fmt.bullet_list(on=0)) 361 362 return ''.join(output) 363 364 # vim: tabstop=4 expandtab shiftwidth=4