events.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. # -*- coding: utf-8 -*-
  2. """
  3. events plugin for Pelican
  4. =========================
  5. This plugin looks for and parses an "events" directory and generates
  6. blog posts with a user-defined event date. (typically in the future)
  7. It also generates an ICalendar v2.0 calendar file.
  8. https://en.wikipedia.org/wiki/ICalendar
  9. Author: Federico Ceratto <federico.ceratto@gmail.com>
  10. Released under AGPLv3+ license, see LICENSE
  11. """
  12. from datetime import datetime, timedelta
  13. from pelican import signals, utils
  14. import icalendar
  15. import logging
  16. import os.path
  17. import pytz
  18. log = logging.getLogger(__name__)
  19. TIME_MULTIPLIERS = {
  20. 'w': 'weeks',
  21. 'd': 'days',
  22. 'h': 'hours',
  23. 'm': 'minutes',
  24. 's': 'seconds'
  25. }
  26. events = []
  27. def parse_tstamp(ev, field_name):
  28. """Parse a timestamp string in format "YYYY-MM-DD HH:MM"
  29. :returns: datetime
  30. """
  31. try:
  32. return datetime.strptime(ev[field_name], '%Y-%m-%d %H:%M')
  33. except Exception, e:
  34. log.error("Unable to parse the '%s' field in the event named '%s': %s" \
  35. % (field_name, ev['title'], e))
  36. raise
  37. def parse_timedelta(ev):
  38. """Parse a timedelta string in format [<num><multiplier> ]*
  39. e.g. 2h 30m
  40. :returns: timedelta
  41. """
  42. chunks = ev['event-duration'].split()
  43. tdargs = {}
  44. for c in chunks:
  45. try:
  46. m = TIME_MULTIPLIERS[c[-1]]
  47. val = int(c[:-1])
  48. tdargs[m] = val
  49. except KeyError:
  50. log.error("""Unknown time multiplier '%s' value in the \
  51. 'event-duration' field in the '%s' event. Supported multipliers \
  52. are: '%s'.""" % (c, ev['title'], ' '.join(TIME_MULTIPLIERS)))
  53. raise RuntimeError("Unknown time multiplier '%s'" % c)
  54. except ValueError:
  55. log.error("""Unable to parse '%s' value in the 'event-duration' \
  56. field in the '%s' event.""" % (c, ev['title']))
  57. raise ValueError("Unable to parse '%s'" % c)
  58. return timedelta(**tdargs)
  59. def parse_article(generator, metadata):
  60. """Collect articles metadata to be used for building the event calendar
  61. :returns: None
  62. """
  63. if 'event-start' not in metadata:
  64. return
  65. dtstart = parse_tstamp(metadata, 'event-start')
  66. if 'event-end' in metadata:
  67. dtend = parse_tstamp(metadata, 'event-end')
  68. elif 'event-duration' in metadata:
  69. dtdelta = parse_timedelta(metadata)
  70. dtend = dtstart + dtdelta
  71. else:
  72. msg = "Either 'event-end' or 'event-duration' must be" + \
  73. " speciefied in the event named '%s'" % metadata['title']
  74. log.error(msg)
  75. raise ValueError(msg)
  76. events.append((dtstart, dtend, metadata))
  77. def generate_ical_file(generator):
  78. """Generate an iCalendar file
  79. """
  80. global events
  81. ics_fname = generator.settings['PLUGIN_EVENTS']['ics_fname']
  82. if not ics_fname:
  83. return
  84. ics_fname = os.path.join(generator.settings['OUTPUT_PATH'], ics_fname)
  85. log.debug("Generating calendar at %s with %d events" % (ics_fname, len(events)))
  86. tz = generator.settings.get('TIMEZONE', 'UTC')
  87. tz = pytz.timezone(tz)
  88. ical = icalendar.Calendar()
  89. ical.add('prodid', '-//My calendar product//mxm.dk//')
  90. ical.add('version', '2.0')
  91. for e in events:
  92. dtstart, dtend, metadata = e
  93. ie = icalendar.Event(
  94. summary=metadata['summary'],
  95. dtstart=dtstart,
  96. dtend=dtend,
  97. dtstamp=metadata['date'],
  98. priority=5,
  99. uid=metadata['title'] + metadata['summary'],
  100. )
  101. if 'event-location' in metadata:
  102. ie.add('location', metadata['event-location'])
  103. ical.add_component(ie)
  104. with open(ics_fname, 'wb') as f:
  105. f.write(ical.to_ical())
  106. def generate_events_list(generator):
  107. """Populate the event_list variable to be used in jinja templates"""
  108. generator.context['events_list'] = sorted(events, reverse=True)
  109. def register():
  110. signals.article_generator_context.connect(parse_article)
  111. signals.article_generator_finalized.connect(generate_ical_file)
  112. signals.article_generator_finalized.connect(generate_events_list)