1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator library 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 search, version 13 import calendar 14 import datetime 15 import re 16 17 __version__ = "0.1" 18 19 # Regular expressions where MoinMoin does not provide the required support. 20 21 category_regexp = None 22 definition_list_regexp = re.compile(ur'^\s+(?P<term>.*?)::\s(?P<desc>.*?)$', re.UNICODE | re.MULTILINE) 23 date_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})', re.UNICODE) 24 month_regexp = re.compile(ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})', re.UNICODE) 25 26 # Utility functions. 27 28 def isMoin15(): 29 return version.release.startswith("1.5.") 30 31 def getCategoryPattern(request): 32 global category_regexp 33 34 try: 35 return request.cfg.cache.page_category_regexact 36 except AttributeError: 37 38 # Use regular expression from MoinMoin 1.7.1 otherwise. 39 40 if category_regexp is None: 41 category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE) 42 return category_regexp 43 44 # The main activity functions. 45 46 def getPages(pagename, request): 47 48 "Return the links minus category links for 'pagename' using the 'request'." 49 50 query = search.QueryParser().parse_query('category:%s' % pagename) 51 if isMoin15(): 52 results = search.searchPages(request, query) 53 results.sortByPagename() 54 else: 55 results = search.searchPages(request, query, "page_name") 56 57 cat_pattern = getCategoryPattern(request) 58 pages = [] 59 for page in results.hits: 60 if not cat_pattern.match(page.page_name): 61 pages.append(page) 62 return pages 63 64 def getPrettyPageName(page): 65 66 "Return a nicely formatted title/name for the given 'page'." 67 68 return page.split_title(force=1).replace("_", " ").replace("/", u" ? ") 69 70 def getEventDetails(page): 71 72 "Return a dictionary of event details from the given 'page'." 73 74 event_details = {} 75 76 if page.pi["format"] == "wiki": 77 for match in definition_list_regexp.finditer(page.body): 78 79 # Permit case-insensitive list terms. 80 81 term = match.group("term").lower() 82 desc = match.group("desc") 83 84 # Special value type handling. 85 86 if term in ("start", "end"): 87 desc = getDate(desc) 88 elif term in ("topics",): 89 desc = [value.strip() for value in desc.split(",")] 90 91 if desc is not None: 92 event_details[term] = desc 93 94 return event_details 95 96 def getDate(s): 97 98 "Parse the string 's', extracting and returning a date string." 99 100 m = date_regexp.search(s) 101 if m: 102 return tuple(map(int, m.groups())) 103 else: 104 return None 105 106 def getMonth(s): 107 108 "Parse the string 's', extracting and returning a month string." 109 110 m = month_regexp.search(s) 111 if m: 112 return tuple(map(int, m.groups())) 113 else: 114 return None 115 116 def getCurrentMonth(): 117 118 "Return the current month as a (year, month) tuple." 119 120 today = datetime.date.today() 121 return (today.year, today.month) 122 123 def monthupdate(date, n): 124 125 "Return 'date' updated by 'n' months." 126 127 if n < 0: 128 fn = prevmonth 129 else: 130 fn = nextmonth 131 132 i = 0 133 while i < abs(n): 134 date = fn(date) 135 i += 1 136 137 return date 138 139 def daterange(first, last): 140 141 "Get the range of dates starting at 'first' and ending on 'last'." 142 143 results = [] 144 step = last > first and 1 or -1 145 146 months_only = len(first) == 2 147 start_year = first[0] 148 end_year = last[0] 149 150 for year in range(start_year, end_year + step, step): 151 if step == 1 and year < end_year: 152 end_month = 12 153 elif step == -1 and year > end_year: 154 end_month = 1 155 else: 156 end_month = last[1] 157 158 if step == 1 and year > start_year: 159 start_month = 1 160 elif step == -1 and year < start_year: 161 start_month = 12 162 else: 163 start_month = first[1] 164 165 for month in range(start_month, end_month + step, step): 166 if months_only: 167 results.append((year, month)) 168 else: 169 if step == 1 and month < end_month: 170 _wd, end_day = calendar.monthrange(year, month) 171 elif step == -1 and month > end_month: 172 end_day = 1 173 else: 174 end_day = last[2] 175 176 if step == 1 and month > start_month: 177 start_day = 1 178 elif step == -1 and month < start_month: 179 _wd, start_day = calendar.monthrange(year, month) 180 else: 181 start_day = first[2] 182 183 for day in range(start_day, end_day + step, step): 184 results.append((year, month, day)) 185 186 return results 187 188 def nextdate(date): 189 190 "Return the date following the given 'date'." 191 192 year, month, day = date 193 _wd, end_day = calendar.monthrange(year, month) 194 if day == end_day: 195 if month == 12: 196 return (year + 1, 1, 1) 197 else: 198 return (year, month + 1, 1) 199 else: 200 return (year, month, day + 1) 201 202 def prevdate(date): 203 204 "Return the date preceding the given 'date'." 205 206 year, month, day = date 207 if day == 1: 208 if month == 1: 209 return (year - 1, 12, 31) 210 else: 211 _wd, end_day = calendar.monthrange(year, month - 1) 212 return (year, month - 1, end_day) 213 else: 214 return (year, month, day - 1) 215 216 def nextmonth(date): 217 218 "Return the (year, month) tuple following 'date'." 219 220 year, month = date 221 if month == 12: 222 return (year + 1, 1) 223 else: 224 return year, month + 1 225 226 def prevmonth(date): 227 228 "Return the (year, month) tuple preceding 'date'." 229 230 year, month = date 231 if month == 1: 232 return (year - 1, 12) 233 else: 234 return year, month - 1 235 236 def getEvents(request, category_names, calendar_start=None, calendar_end=None): 237 238 """ 239 Using the 'request', generate a list of events found on pages belonging to 240 the specified 'category_names', using the optional 'calendar_start' and 241 'calendar_end' month tuples of the form (year, month) to indicate a window 242 of interest. 243 244 Return a list of events, a dictionary mapping months to event lists (within 245 the window of interest), a list of all events within the window of interest, 246 the earliest month of an event within the window of interest, and the latest 247 month of an event within the window of interest. 248 """ 249 250 events = [] 251 shown_events = {} 252 all_shown_events = [] 253 254 earliest = None 255 latest = None 256 257 for category_name in category_names: 258 259 # Get the pages and page names in the category. 260 261 pages_in_category = getPages(category_name, request) 262 263 # Visit each page in the category. 264 265 for page_in_category in pages_in_category: 266 pagename = page_in_category.page_name 267 268 # Get a real page, not a result page. 269 270 real_page_in_category = Page(request, pagename) 271 event_details = getEventDetails(real_page_in_category) 272 273 # Define the event as the page together with its details. 274 275 event = (real_page_in_category, event_details) 276 events.append(event) 277 278 # Test for the suitability of the event. 279 280 if event_details.has_key("start") and event_details.has_key("end"): 281 282 start_month = event_details["start"][:2] 283 end_month = event_details["end"][:2] 284 285 # Compare the months of the dates to the requested calendar 286 # window, if any. 287 288 if (calendar_start is None or end_month >= calendar_start) and \ 289 (calendar_end is None or start_month <= calendar_end): 290 291 all_shown_events.append(event) 292 293 if earliest is None or start_month < earliest: 294 earliest = start_month 295 if latest is None or end_month > latest: 296 latest = end_month 297 298 # Store the event in the month-specific dictionary. 299 300 first = max(start_month, calendar_start or start_month) 301 last = min(end_month, calendar_end or end_month) 302 303 for event_month in daterange(first, last): 304 if not shown_events.has_key(event_month): 305 shown_events[event_month] = [] 306 shown_events[event_month].append(event) 307 308 return events, shown_events, all_shown_events, earliest, latest 309 310 # vim: tabstop=4 expandtab shiftwidth=4