nuovo sito fatto con pelican
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
6.6KB

  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. from collections import namedtuple, defaultdict
  15. import icalendar
  16. import logging
  17. import os.path
  18. import pytz
  19. log = logging.getLogger(__name__)
  20. TIME_MULTIPLIERS = {
  21. 'w': 'weeks',
  22. 'd': 'days',
  23. 'h': 'hours',
  24. 'm': 'minutes',
  25. 's': 'seconds'
  26. }
  27. events = []
  28. localized_events = defaultdict(list)
  29. Event = namedtuple("Event", "dtstart dtend metadata rrule")
  30. def parse_tstamp(ev, field_name):
  31. """Parse a timestamp string in format "YYYY-MM-DD HH:MM"
  32. :returns: datetime
  33. """
  34. try:
  35. return datetime.strptime(ev[field_name], '%Y-%m-%d %H:%M')
  36. except Exception as e:
  37. log.error("Unable to parse the '%s' field in the event named '%s': %s" \
  38. % (field_name, ev['title'], e))
  39. raise
  40. def parse_timedelta(ev):
  41. """Parse a timedelta string in format [<num><multiplier> ]*
  42. e.g. 2h 30m
  43. :returns: timedelta
  44. """
  45. chunks = ev['event-duration'].split()
  46. tdargs = {}
  47. for c in chunks:
  48. try:
  49. m = TIME_MULTIPLIERS[c[-1]]
  50. val = int(c[:-1])
  51. tdargs[m] = val
  52. except KeyError:
  53. log.error("""Unknown time multiplier '%s' value in the \
  54. 'event-duration' field in the '%s' event. Supported multipliers \
  55. are: '%s'.""" % (c, ev['title'], ' '.join(TIME_MULTIPLIERS)))
  56. raise RuntimeError("Unknown time multiplier '%s'" % c)
  57. except ValueError:
  58. log.error("""Unable to parse '%s' value in the 'event-duration' \
  59. field in the '%s' event.""" % (c, ev['title']))
  60. raise ValueError("Unable to parse '%s'" % c)
  61. return timedelta(**tdargs)
  62. def parse_recursion(ev, field_name):
  63. """
  64. Parse information about recurring events and return frequency of event and untill time.
  65. """
  66. chunks = ev[field_name].split()
  67. freq = chunks[0].upper()
  68. if 'until' in chunks:
  69. until = datetime.strptime(chunks[-1], '%Y-%m-%d')
  70. rrule = [freq, until]
  71. else:
  72. rrule = [freq]
  73. return rrule
  74. def parse_article(generator, metadata):
  75. """Collect articles metadata to be used for building the event calendar
  76. :returns: None
  77. """
  78. if 'event-start' not in metadata:
  79. return
  80. dtstart = parse_tstamp(metadata, 'event-start')
  81. rrule=[]
  82. if 'event-end' in metadata:
  83. dtend = parse_tstamp(metadata, 'event-end')
  84. elif 'event-duration' in metadata:
  85. dtdelta = parse_timedelta(metadata)
  86. dtend = dtstart + dtdelta
  87. if 'event-recurring' in metadata:
  88. rrule = parse_recursion(metadata, 'event-recurring')
  89. else:
  90. msg = "Either 'event-end' or 'event-duration' must be" + \
  91. " speciefied in the event named '%s'" % metadata['title']
  92. log.error(msg)
  93. raise ValueError(msg)
  94. events.append(Event(dtstart, dtend, metadata, rrule))
  95. def generate_ical_file(generator):
  96. """Generate an iCalendar file
  97. """
  98. global events
  99. ics_fname = generator.settings['PLUGIN_EVENTS']['ics_fname']
  100. if not ics_fname:
  101. return
  102. if not os.path.exists(generator.settings['OUTPUT_PATH']):
  103. os.makedirs(generator.settings['OUTPUT_PATH'])
  104. ics_fname = os.path.join(generator.settings['OUTPUT_PATH'], ics_fname)
  105. log.debug("Generating calendar at %s with %d events" % (ics_fname, len(events)))
  106. tz = generator.settings.get('TIMEZONE', 'UTC')
  107. tz = pytz.timezone(tz)
  108. ical = icalendar.Calendar()
  109. ical.add('prodid', '-//My calendar product//mxm.dk//')
  110. ical.add('version', '2.0')
  111. DEFAULT_LANG = generator.settings['DEFAULT_LANG']
  112. curr_events = events if not localized_events else localized_events[DEFAULT_LANG]
  113. for e in curr_events:
  114. #force convert to ical format here, because otherwise it doesn't happen?
  115. from icalendar import vDatetime as vd
  116. dtend = vd(e.dtend).to_ical()
  117. dtstart = vd(e.dtstart).to_ical()
  118. dtstamp = vd (e.metadata['date']).to_ical()
  119. ie = icalendar.Event(
  120. summary=e.metadata['title'],
  121. dtstart=dtstart,
  122. dtend=dtend,
  123. dtstamp= dtstamp,
  124. priority=5,
  125. uid=e.metadata['title'],
  126. )
  127. if 'event-location' in e.metadata:
  128. ie.add('location', e.metadata['event-location'])
  129. if 'event-recurring' in e.metadata:
  130. if len(e.rrule)>=2:
  131. ie.add('rrule', {'freq':e.rrule[0],'until':e.rrule[1]})
  132. else:
  133. ie.add('rrule', {'freq':e.rrule[0]})
  134. ical.add_component(ie)
  135. with open(ics_fname, 'wb') as f:
  136. f.write(ical.to_ical())
  137. def generate_localized_events(generator):
  138. """ Generates localized events dict if i18n_subsites plugin is active """
  139. if "i18n_subsites" in generator.settings["PLUGINS"]:
  140. if not os.path.exists(generator.settings['OUTPUT_PATH']):
  141. os.makedirs(generator.settings['OUTPUT_PATH'])
  142. for e in events:
  143. if "lang" in e.metadata:
  144. localized_events[e.metadata["lang"]].append(e)
  145. else:
  146. log.debug("event %s contains no lang attribute" % (e.metadata["title"],))
  147. def generate_events_list(generator):
  148. """Populate the event_list variable to be used in jinja templates"""
  149. if not localized_events:
  150. generator.context['events_list'] = sorted(events, reverse = True,
  151. key=lambda ev: (ev.dtstart, ev.dtend))
  152. else:
  153. generator.context['events_list'] = {k: sorted(v, reverse = True,
  154. key=lambda ev: (ev.dtstart, ev.dtend))
  155. for k, v in localized_events.items()}
  156. def initialize_events(article_generator):
  157. """
  158. Clears the events list before generating articles to properly support plugins with
  159. multiple generation passes like i18n_subsites
  160. """
  161. del events[:]
  162. localized_events.clear()
  163. def register():
  164. signals.article_generator_init.connect(initialize_events)
  165. signals.article_generator_context.connect(parse_article)
  166. signals.article_generator_finalized.connect(generate_localized_events)
  167. signals.article_generator_finalized.connect(generate_ical_file)
  168. signals.article_generator_finalized.connect(generate_events_list)