Federico Ceratto лет назад: 9
Родитель
Сommit
9aac5bc39d
4 измененных файлов с 241 добавлено и 0 удалено
  1. 53 0
      events/Readme.rst
  2. 1 0
      events/__init__.py
  3. 151 0
      events/events.py
  4. 36 0
      events/events_list.html

+ 53 - 0
events/Readme.rst

@@ -0,0 +1,53 @@
+events
+----
+
+This plugin scans blog posts for an events.
+It also generates an ``.ical`` calendar file.
+
+Dependencies
+------------
+
+This plugin depends on the ``icalendar`` package, which can be installed
+using APT or RPM or, if you are unlucky, via pip::
+
+    pip install icalendar
+
+Usage
+-----
+
+Add the following to pelicanconf.py::
+    PLUGIN_EVENTS = {
+        'ics_fname': 'calendar.ics',
+    }
+
+Create articles and usual and add the "event-start" metadata to turn them into
+events. The event start is independent of the article "date".
+"event-start" is in "YYYY-MM-DD hh:mm" format.
+Also add "event-end", in the same format, or "event-duration" as a number
+followed by a dimension:
+
+w: weeks
+d: days
+h: hours
+m: minutes
+s: seconds
+
+You can also specify an optional "location"
+
+Example in ReST format::
+
+    :event-start: 2015-01-21 10:30
+    :event-duration: 2h
+    :location: somewhere
+
+
+To generate an sorted event list in a dedicated page copy the events_list.html
+template under the templates directory in your theme, then create a page:
+
+content/pages/events_list.rst::
+
+ Events list
+ ###########
+ :slug: events-list
+ :summary:
+ :template: events_list

+ 1 - 0
events/__init__.py

@@ -0,0 +1 @@
+from .events import *

+ 151 - 0
events/events.py

@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+"""
+events plugin for Pelican
+=========================
+
+This plugin looks for and parses an "events" directory and generates
+blog posts with a user-defined event date. (typically in the future)
+It also generates an ICalendar v2.0 calendar file.
+https://en.wikipedia.org/wiki/ICalendar
+
+
+Author: Federico Ceratto <federico.ceratto@gmail.com>
+Released under AGPLv3+ license, see LICENSE
+"""
+
+from datetime import datetime, timedelta
+from pelican import signals, utils
+import icalendar
+import logging
+import os.path
+import pytz
+
+log = logging.getLogger(__name__)
+
+TIME_MULTIPLIERS = {
+    'w': 'weeks',
+    'd': 'days',
+    'h': 'hours',
+    'm': 'minutes',
+    's': 'seconds'
+}
+
+events = []
+
+
+def parse_tstamp(ev, field_name):
+    """Parse a timestamp string in format "YYYY-MM-DD HH:MM"
+
+    :returns: datetime
+    """
+    try:
+        return datetime.strptime(ev[field_name], '%Y-%m-%d %H:%M')
+    except Exception, e:
+        log.error("Unable to parse the '%s' field in the event named '%s': %s" \
+            % (field_name, ev['title'], e))
+        raise
+
+
+def parse_timedelta(ev):
+    """Parse a timedelta string in format [<num><multiplier> ]*
+    e.g. 2h 30m
+
+    :returns: timedelta
+    """
+
+    chunks = ev['event-duration'].split()
+    tdargs = {}
+    for c in chunks:
+        try:
+            m = TIME_MULTIPLIERS[c[-1]]
+            val = int(c[:-1])
+            tdargs[m] = val
+        except KeyError:
+            log.error("""Unknown time multiplier '%s' value in the \
+'event-duration' field in the '%s' event. Supported multipliers \
+are: '%s'.""" % (c, ev['title'], ' '.join(TIME_MULTIPLIERS)))
+            raise RuntimeError("Unknown time multiplier '%s'" % c)
+        except ValueError:
+            log.error("""Unable to parse '%s' value in the 'event-duration' \
+field in the '%s' event.""" % (c, ev['title']))
+            raise ValueError("Unable to parse '%s'" % c)
+
+
+    return timedelta(**tdargs)
+
+
+def parse_article(generator, metadata):
+    """Collect articles metadata to be used for building the event calendar
+
+    :returns: None
+    """
+    if 'event-start' not in metadata:
+        return
+
+    dtstart = parse_tstamp(metadata, 'event-start')
+
+    if 'event-end' in metadata:
+        dtend = parse_tstamp(metadata, 'event-end')
+
+    elif 'event-duration' in metadata:
+        dtdelta = parse_timedelta(metadata)
+        dtend = dtstart + dtdelta
+
+    else:
+        msg = "Either 'event-end' or 'event-duration' must be" + \
+            " speciefied in the event named '%s'" % metadata['title']
+        log.error(msg)
+        raise ValueError(msg)
+
+    events.append((dtstart, dtend, metadata))
+
+
+def generate_ical_file(generator):
+    """Generate an iCalendar file
+    """
+    global events
+    ics_fname = generator.settings['PLUGIN_EVENTS']['ics_fname']
+    if not ics_fname:
+        return
+
+    ics_fname = os.path.join(generator.settings['OUTPUT_PATH'], ics_fname)
+    log.debug("Generating calendar at %s with %d events" % (ics_fname, len(events)))
+
+    tz = generator.settings.get('TIMEZONE', 'UTC')
+    tz = pytz.timezone(tz)
+
+    ical = icalendar.Calendar()
+    ical.add('prodid', '-//My calendar product//mxm.dk//')
+    ical.add('version', '2.0')
+
+    for e in events:
+        dtstart, dtend, metadata = e
+
+        ie = icalendar.Event(
+            summary=metadata['summary'],
+            dtstart=dtstart,
+            dtend=dtend,
+            dtstamp=metadata['date'],
+            priority=5,
+            uid=metadata['title'] + metadata['summary'],
+        )
+        if 'event-location' in metadata:
+            ie.add('location', metadata['event-location'])
+
+        ical.add_component(ie)
+
+    with open(ics_fname, 'wb') as f:
+        f.write(ical.to_ical())
+
+
+
+def generate_events_list(generator):
+    """Populate the event_list variable to be used in jinja templates"""
+    generator.context['events_list'] = sorted(events, reverse=True)
+
+def register():
+    signals.article_generator_context.connect(parse_article)
+    signals.article_generator_finalized.connect(generate_ical_file)
+    signals.article_generator_finalized.connect(generate_events_list)
+
+

+ 36 - 0
events/events_list.html

@@ -0,0 +1,36 @@
+
+{% extends "base.html" %}
+
+{% block title %} Events list - {{ SITENAME }}{% endblock %}
+
+{% block content %}
+
+    {% if events_list %}
+    <ul class="post-list">
+    {% for evstart, evend, event in events_list %}
+      <li>
+        <p>
+          <a href="../{{event.slug}}.html">
+            <b>{{event['title']}}</b>
+          </a>
+        </p>
+        <p>
+        {% if evstart.date() == evend.date() %}
+        From {{evstart}} to {{evend.time()}}
+        {% else %}
+        From {{evstart}} to {{evend}}
+        {% endif %}
+        </p>
+
+        {% if event.location %}
+        <p>Location: {{event.location}}</p>
+        {% endif %}
+
+        <p>{{event['summary']}}</p>
+
+      </li>
+    {% endfor %}
+    </ul>
+    {% endif %}
+
+{% endblock %}