EventAggregator

macros/EventAggregator.py

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