# HG changeset patch # User Paul Boddie # Date 1244324234 -7200 # Node ID 1cbad5c4b7f0c02d44337ce6fe8ad6cba78925f8 # Parent 8337b0b6177d7cc940658aa02ad359f77bef012b Moved event body manipulation to the EventAggregatorSupport module, employing an approach similar to that used when retrieving event details from page bodies. Changed the definition list regular expression to support recognition of values in commented-out regions, meaning that such regions must now be recognised and discarded when retrieving event details from pages. Changed the parameter values sent by the macro in links from day numbers in calendar views. Introduced better behaviour when interpreting request parameters in the EventAggregatorNewEvent action, providing the current year as the default start year. Removed the redirect for cancelled page edits since it gives the wrong impression about whether a page has been created (which has already happened by the time the user gets to edit the page). Added documentation for the EventAggregatorNewEvent action. Updated documentation for the EventAggregator macro. Updated the release information. Added package-related files. diff -r 8337b0b6177d -r 1cbad5c4b7f0 EventAggregatorSupport.py --- a/EventAggregatorSupport.py Sat Jun 06 02:17:51 2009 +0200 +++ b/EventAggregatorSupport.py Sat Jun 06 23:37:14 2009 +0200 @@ -27,7 +27,14 @@ # Regular expressions where MoinMoin does not provide the required support. category_regexp = None -definition_list_regexp = re.compile(ur'^\s+(?P.*?)::\s(?P.*?)$', re.UNICODE | re.MULTILINE) + +# Page parsing. + +definition_list_regexp = re.compile(ur'(?P^(?P#*)\s+(?P.*?)::\s)(?P.*?)$', re.UNICODE | re.MULTILINE) +category_membership_regexp = re.compile(ur"^\s*((Category\S+)(\s+Category\S+)*)\s*$", re.MULTILINE | re.UNICODE) + +# Value parsing. + date_regexp = re.compile(ur'(?P[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})', re.UNICODE) month_regexp = re.compile(ur'(?P[0-9]{4})-(?P[0-9]{2})', re.UNICODE) verbatim_regexp = re.compile(ur'(?:' @@ -144,6 +151,12 @@ return "".join([s for s in verbatim_regexp.split(text) if s is not None]) +def getEncodedWikiText(text): + + "Encode the given 'text' in a verbatim representation." + + return "<>" % text + def getFormat(page): "Get the format used on 'page'." @@ -162,6 +175,11 @@ if getFormat(page) == "wiki": for match in definition_list_regexp.finditer(page.get_raw_body()): + # Skip commented-out items. + + if match.group("optcomment"): + continue + # Permit case-insensitive list terms. term = match.group("term").lower() @@ -189,6 +207,71 @@ return event_details +def setEventDetails(body, event_details): + + """ + Set the event details in the given page 'body' using the 'event_details' + dictionary, returning the new body text. + """ + + new_body_parts = [] + end_of_last_match = 0 + + for match in definition_list_regexp.finditer(body): + + # Add preceding text to the new body. + + new_body_parts.append(body[end_of_last_match:match.start()]) + end_of_last_match = match.end() + + # Get the matching regions, adding the term to the new body. + + new_body_parts.append(match.group("wholeterm")) + + # Permit case-insensitive list terms. + + term = match.group("term").lower() + desc = match.group("desc") + + # Special value type handling. + + if event_details.has_key(term): + + # Dates. + + if term in ("start", "end"): + desc = desc.replace("YYYY-MM-DD", event_details[term]) + + # Lists (whose elements may be quoted). + + elif term in ("topics", "categories"): + desc = ", ".join(getEncodedWikiText(event_details[term])) + + # Labels which may well be quoted. + + elif term in ("title", "summary", "description"): + desc = getEncodedWikiText(event_details[term]) + + new_body_parts.append(desc) + + else: + new_body_parts.append(body[end_of_last_match:]) + + return "".join(new_body_parts) + +def setCategoryMembership(body, category_names): + + """ + Set the category membership in the given page 'body' using the specified + 'category_names' and returning the new body text. + """ + + match = category_membership_regexp.search(body) + if match: + return "".join([body[:match.start()], " ".join(category_names), body[match.end():]]) + else: + return body + def getEventSummary(event_page, event_details): """ diff -r 8337b0b6177d -r 1cbad5c4b7f0 PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PKG-INFO Sat Jun 06 23:37:14 2009 +0200 @@ -0,0 +1,23 @@ +Metadata-Version: 1.1 +Name: EventAggregator +Version: 0.3 +Author: Paul Boddie +Author-email: paul at boddie org uk +Maintainer: Paul Boddie +Maintainer-email: paul at boddie org uk +Home-page: http://moinmo.in/MacroMarket/EventAggregator +Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.3.tar.gz +Summary: Aggregate event data and display it in an event calendar (or summarise it in iCalendar and RSS resources) +License: GPL (version 2 or later) +Description: The EventAggregator macro for MoinMoin can be used to display event + calendars or listings which obtain their data from pages belonging to + specific categories. The EventAggregatorSummary action provides + summaries of events stored in a Wiki, and the EventAggregatorNewEvent + action provides a convenient way of adding new events. +Keywords: MoinMoin Wiki calendar event RSS iCalendar +Requires: MoinMoin +Classifier: Development Status :: 3 - Alpha +Classifier: License :: OSI Approved :: GNU General Public License (GPL) +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content diff -r 8337b0b6177d -r 1cbad5c4b7f0 README.txt --- a/README.txt Sat Jun 06 02:17:51 2009 +0200 +++ b/README.txt Sat Jun 06 23:37:14 2009 +0200 @@ -14,6 +14,12 @@ the request as URL or form parameters: these restrict the extent of each generated summary. +The EventAggregatorNewEvent action can be used to conveniently create new +event pages, displaying a simple form which can be filled out in order to +provide elementary event details such as the event title or summary, the +categories to which the page will be assigned, and the start and end dates of +the event. + Installation ------------ @@ -54,7 +60,7 @@ This ensures that the styles are made available to the browser. -To install the action in a Wiki, consider using the instactions script provided: +To install the actions in a Wiki, consider using the instactions script provided: ./instactions path-to-wiki @@ -93,8 +99,8 @@ See pages/HelpOnEventAggregator for more detailed information. -Using the Action ----------------- +Using the Actions +----------------- To obtain an iCalendar summary, the EventAggregatorSummary action can be selected from the actions menu on any page. Alternatively, a collection of @@ -102,6 +108,13 @@ See pages/HelpOnEventAggregatorSummary for more detailed information. +To create new events using the EventAggregatorNewEvent action, the appropriate +menu entry can be selected in the actions menu. Alternatively, clicking on a +day number in a calendar view will invoke the action and pre-fill the form +with the start date set to the selected day from the calendar. + +See pages/HelpOnEventAggregatorNewEvent for more detailed information. + Recommended Software -------------------- @@ -145,6 +158,7 @@ * Added a parameter to the EventAggregatorSummary action to select the source of event descriptions for the RSS feed. * Updated the documentation to cover the RSS support. + * Added the EventAggregatorNewEvent action. New in EventAggregator 0.2 (Changes since EventAggregator 0.1) -------------------------------------------------------------- diff -r 8337b0b6177d -r 1cbad5c4b7f0 actions/EventAggregatorNewEvent.py --- a/actions/EventAggregatorNewEvent.py Sat Jun 06 02:17:51 2009 +0200 +++ b/actions/EventAggregatorNewEvent.py Sat Jun 06 23:37:14 2009 +0200 @@ -25,17 +25,14 @@ "An event creation dialogue requesting various parameters." - # NOTE: These patterns, used to replace text in the template, must - # NOTE: correspond with the template and the recognised labels found in - # NOTE: EventAggregatorSummary.getEventDetails. + def _get_selected(self, month, input_month): + return input_month is not None and month == input_month and 'selected="selected"' or '' - start_pattern = re.compile("(?<= start:: ).*?(?Pyyyy-mm-dd)") - end_pattern = re.compile("(?<= end:: ).*?(?Pyyyy-mm-dd)") - topics_pattern = re.compile(r"(?<= categories:: )\s*(.*?)$|(?<= topics:: )\s*(.*?)$", re.MULTILINE | re.UNICODE) - category_pattern = re.compile(r"^\s*((Category\S+)(\s+Category\S+)*)\s*$", re.MULTILINE | re.UNICODE) - - def _get_selected(self, month, input_month): - return (("%02d" % month) == input_month) and 'selected="selected"' or '' + def _get_input(self, form, name, default, error): + try: + return int(form.get(name, [None])[0] or default) + except ValueError: + return error def get_form_html(self, buttons_html): _ = self._ @@ -58,8 +55,10 @@ end_month_list = [] end_month_list.append('') - start_month = form.get("start-month", [None])[0] - end_month = form.get("end-month", [None])[0] + start_month = self._get_input(form, "start-month", 0, None) + end_month = self._get_input(form, "end-month", 0, None) + start_year = self._get_input(form, "start-year", EventAggregatorSupport.getCurrentYear(), None) + end_year = self._get_input(form, "end-year", 0, None) # Prepare month lists, selecting specified months. @@ -78,7 +77,7 @@ "end_month_list" : "\n".join(end_month_list), "start_label" : _("Start date (day, month, year)"), "start_day_default" : form.get("start-day", [""])[0], - "start_year_default" : form.get("start-year", [""])[0], + "start_year_default" : start_year, "end_label" : _("End date (day, month, year)"), "end_day_default" : form.get("end-day", [""])[0], "end_year_default" : form.get("end-year", [""])[0], @@ -159,17 +158,18 @@ "Create an event page using the 'request'." _ = request.getText + form = request.form - category_names = request.form.get("category", []) - title = request.form.get("title", [None])[0] + category_names = form.get("category", []) + title = form.get("title", [None])[0] try: - start_day = int(request.form.get("start-day", [None])[0] or 0) - start_month = int(request.form.get("start-month", [None])[0] or 0) - start_year = int(request.form.get("start-year", [None])[0] or 0) - end_day = int(request.form.get("end-day", [None])[0] or start_day) - end_month = int(request.form.get("end-month", [None])[0] or start_month) - end_year = int(request.form.get("end-year", [None])[0] or start_year) + start_day = self._get_input(form, "start-day", 0, None) + start_month = self._get_input(form, "start-month", 0, None) + start_year = self._get_input(form, "start-year", 0, None) + end_day = self._get_input(form, "end-day", start_day, None) + end_month = self._get_input(form, "end-month", start_month, None) + end_year = self._get_input(form, "end-year", start_year, None) start_year, start_month, start_day = self.constrain_date(start_year, start_month, start_day) end_year, end_month, end_day = self.constrain_date(end_year, end_month, end_day) @@ -186,35 +186,25 @@ page = PageEditor(request, getattr(request.cfg, "event_aggregator_new_event_template", "EventTemplate")) body = page.get_raw_body() - # Load the new page and replace the placeholders. + # Load the new page and replace the event details in the body. new_page = PageEditor(request, title) if new_page.exists(): return 0, _("The specified page already exists. Please choose another name.") - # NOTE: The placeholders must match the template. - - body = self.replace_placeholder(body, self.start_pattern, start_date, 1) - body = self.replace_placeholder(body, self.end_pattern, end_date, 1) - body = self.replace_placeholder(body, self.category_pattern, " ".join(category_names)) - - new_page.saveText(body, 0) + if EventAggregatorSupport.getFormat(page) == "wiki": + event_details = {"start" : start_date, "end" : end_date, "title" : title, "summary" : title} + body = EventAggregatorSupport.setEventDetails(body, event_details) + body = EventAggregatorSupport.setCategoryMembership(body, category_names) + new_page.saveText(body, 0) # Redirect and return success. - query = {'action' : 'edit', 'backto' : self.pagename} + query = {'action' : 'edit'} request.http_redirect(new_page.url(request, query)) return 1, None - def replace_placeholder(self, body, pattern, value, lower=0): - match = pattern.search(lower and body.lower() or body) - if match: - start, end = match.span() - return body[:start] + value + body[end:] - else: - return body - def make_date_string(self, year, month, day): return "%s-%s-%s" % ( year and ("%04d" % year) or "YYYY", diff -r 8337b0b6177d -r 1cbad5c4b7f0 macros/EventAggregator.py --- a/macros/EventAggregator.py Sat Jun 06 02:17:51 2009 +0200 +++ b/macros/EventAggregator.py Sat Jun 06 23:37:14 2009 +0200 @@ -360,7 +360,7 @@ # Make a link to a new event action. - new_event_link = "action=EventAggregatorNewEvent&start-day=%02d&start-month=%02d&start-year=%04d" % ( + new_event_link = "action=EventAggregatorNewEvent&start-day=%d&start-month=%d&start-year=%d" % ( day, month, year) # Output the day number. diff -r 8337b0b6177d -r 1cbad5c4b7f0 pages/HelpOnEventAggregator --- a/pages/HelpOnEventAggregator Sat Jun 06 02:17:51 2009 +0200 +++ b/pages/HelpOnEventAggregator Sat Jun 06 23:37:14 2009 +0200 @@ -13,9 +13,13 @@ <> -Each event must be created on a new page belonging to the appropriate event category. For CategoryEvents, you can do this by filling out and submitting this form: +Each event must be created on a new page belonging to the appropriate event category. The following action can be used to create a new event page (using !EventAggregatorNewEvent): + +(!) <> -<> +Since each event is represented by a page, creating a new page based on an appropriate template is also sufficient. For pages belonging to CategoryEvents, you can do this by filling out and submitting this form (which uses EventTemplate): + +<> The event page describes the event in more detail, and the start and end dates of the event must be specified in a definition list so that they can be read from the page and displayed by the !EventAggregator. The EventTemplate provides some guidance, and all you need to do is to replace the `YYYY-MM-DD` placeholders with actual year, month and day values. For example: @@ -124,4 +128,5 @@ == See Also == + * HelpOnEventAggregatorNewEvent - an action providing a form for creating new events conveniently * HelpOnEventAggregatorSummary - an action producing iCalendar event summaries diff -r 8337b0b6177d -r 1cbad5c4b7f0 pages/HelpOnEventAggregatorNewEvent --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pages/HelpOnEventAggregatorNewEvent Sat Jun 06 23:37:14 2009 +0200 @@ -0,0 +1,26 @@ +##master-page:HelpTemplate +##master-date:Unknown-Date +#format wiki +#language en + +== EventAggregatorNewEvent == + +The !EventAggregatorNewEvent action provides support for creating new events more conveniently than manually editing an event page based on a template. The action can be selected from the actions menu on any page, or it can be invoked by selecting a day number in an !EventAggregator calendar view. + +Upon invoking the action, a form will be displayed requesting values for the following items: + + * The title or summary of the event; this will be used as the page name. + * The start date of the event (optional). + * The end date of the event (optional). + +When the form is submitted, a new page will be created and the supplied details inserted into the appropriate locations in the page. The new page will then be opened for further editing. + +If a page already exists with a name identical to that provided as the event title, the form will prompt for an alternative title. If no end date is provided, the start and end dates will be identical in the newly created page. + +/!\ If no changes are made to the new page during editing and the editing process is cancelled, the new page will still exist. Event pages created in error should therefore be deleted manually. + +A technical note: new event pages are derived from the EventTemplate page by default; this can be overridden by setting the `event_aggregator_new_event_template` configuration setting for the Wiki. + +== See Also == + + * HelpOnEventAggregator - a macro producing event calendars and listings