diff --git a/README.md b/README.md index f9cf352..3f04282 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,15 @@ firefox output/index.html Also, `make help` is your friend. **Morte ai nemici dell'UTF-8** + +Aggiungere un talk +-------------------- + +```sh +cp -r talks/_talk_example/ talks/MIOTALK/ +vim talks/MIOTALK/meta.yaml +``` + +Quindi rifai `make publish` come spiegato prima: l'output ti informa di eventuali errori nei campi o +sovrapposizioni con altri talk, leggilo! + diff --git a/content/pages/programma.en.rst b/content/pages/programma.en.rst index 668cf5c..44eda1b 100644 --- a/content/pages/programma.en.rst +++ b/content/pages/programma.en.rst @@ -17,6 +17,8 @@ appreciated! Hackmeeting (still) hasn't a proper translation system, but you can find a bunch of people to ask to do translations when you need it. +.. talkgrid:: + .. talklist:: diff --git a/content/pages/programma.rst b/content/pages/programma.rst index daabcda..a98196e 100644 --- a/content/pages/programma.rst +++ b/content/pages/programma.rst @@ -12,5 +12,7 @@ Leggi l'`invito a presentare dei contenuti `_, fatti coraggio e proponi il tuo contenuto in `mailing list <{filename}contatti.rst>`_ +.. talkgrid:: + .. talklist:: diff --git a/plugins/talks.py b/plugins/talks.py index 8c55b42..971d37b 100644 --- a/plugins/talks.py +++ b/plugins/talks.py @@ -10,7 +10,10 @@ import logging import re import datetime import shutil +import time +from copy import copy +import markdown from docutils import nodes from docutils.parsers.rst import directives, Directive @@ -19,6 +22,9 @@ import jinja2 TALKS_PATH = 'talks' +TALK_ATTACHMENT_PATH = 'res' +TALK_ICS = 'schedule.ics' +GRID_STEP = 15 def memoize(function): @@ -44,6 +50,34 @@ def get_talk_names(): ] +def all_talks(): + return [get_talk_data(tn) for tn in get_talk_names()] + + +def unique_attr(iterable, attr): + return {x[attr] for x in iterable + if attr in x} + + +@memoize +def get_global_data(): + fname = os.path.join(TALKS_PATH, 'meta.yaml') + if not os.path.isfile(fname): + return None + with io.open(fname, encoding='utf8') as buf: + try: + data = yaml.load(buf) + except Exception: + logging.exception("Syntax error reading %s; skipping", fname) + return None + if data is None: + return None + if 'startdate' not in data: + logging.error("Missing startdate in global data") + data['startdate'] = datetime.datetime.now() + return data + + @memoize def get_talk_data(talkname): fname = os.path.join(TALKS_PATH, talkname, 'meta.yaml') @@ -68,19 +102,32 @@ def get_talk_data(talkname): .format(talkname)) data['duration'] = 50 data['duration'] = int(data['duration']) + if data['duration'] < GRID_STEP: + logging.info("Talk <{}> lasts only {} minutes; changing to {}" + .format(talkname, data['duration'], GRID_STEP)) + data['duration'] = GRID_STEP + if 'links' not in data or not data['links']: + data['links'] = [] + if 'contacts' not in data or not data['contacts']: + data['contacts'] = [] + if 'needs' not in data or not data['needs']: + data['needs'] = [] if 'room' not in data: logging.warn("Talk <{}> has no `room` field".format(talkname)) if 'time' not in data or 'day' not in data: logging.warn("Talk <{}> has no `time` or `day`".format(talkname)) - if 'time' in data: + if 'day' in data: + data['day'] = get_global_data()['startdate'] + datetime.timedelta(days=data['day']) + if 'time' in data and 'day' in data: timeparts = re.findall(r'\d+', str(data['time'])) if 4 > len(timeparts) > 0: timeparts = [int(p) for p in timeparts] - data['time'] = datetime.time(*timeparts) + data['time'] = datetime.datetime.combine(data['day'], + datetime.time(*timeparts)) else: logging.error("Talk <{}> has malformed `time`".format(talkname)) data['id'] = talkname - resdir = os.path.join(TALKS_PATH, talkname, 'res') + resdir = os.path.join(TALKS_PATH, talkname, TALK_ATTACHMENT_PATH) if os.path.isdir(resdir) and os.listdir(resdir): data['resources'] = resdir return data @@ -90,6 +137,9 @@ jinja_env = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.join(TALKS_PATH, '_templates')), autoescape=True, ) +jinja_env.filters['markdown'] = lambda text: \ + jinja2.Markup(markdown.Markdown(extensions=['meta']). + convert(text)) class TalkListDirective(Directive): @@ -123,7 +173,94 @@ class TalkDirective(Directive): format='html') ] -# TODO: TalkGridDirective (griglia completa) + +class TalkGridDirective(Directive): + '''A complete grid''' + required_arguments = 0 + + optional_arguments = 0 + final_argument_whitespace = True + has_content = True + + def run(self): + tmpl = jinja_env.get_template('grid.html') + output = [] + days = unique_attr(all_talks(), 'day') + for day in sorted(days): + talks = {talk['id'] for talk in all_talks() + if talk.get('day', None) == day + and 'time' in talk + and 'room' in talk} + if not talks: + continue + talks = [get_talk_data(t) for t in talks] + rooms = tuple(sorted(unique_attr(talks, 'room'))) + mintime = min({talk['time'].hour * 60 + + talk['time'].minute + for talk in talks}) // GRID_STEP * GRID_STEP + maxtime = max({talk['time'].hour * 60 + + talk['time'].minute + + talk['duration'] + for talk in talks}) + times = {} + + for t in range(mintime, maxtime, GRID_STEP): + times[t] = [None] * len(rooms) + for talk in sorted(talks, key=lambda x: x['time']): + talktime = talk['time'].hour * 60 + talk['time'].minute + position = talktime // GRID_STEP * GRID_STEP # round + assert position in times + roomnum = rooms.index(talk['room']) + if times[position][roomnum] is not None: + logging.error("Talk {} and {} overlap! " + .format(times[position][roomnum]['id'], + talk['id'])) + continue + times[position][roomnum] = copy(talk) + times[position][roomnum]['skip'] = False + for i in range(1, talk['duration'] // GRID_STEP): + times[position + i*GRID_STEP][roomnum] = copy(talk) + times[position + i*GRID_STEP][roomnum]['skip'] = True + + render = tmpl.render(times=times, + rooms=rooms, + mintime=mintime, maxtime=maxtime, + timestep=GRID_STEP, + ) + output.append(nodes.raw('', u'

%s

' % + day.strftime('%A %d').decode('utf8').title(), + format='html')) + output.append(nodes.raw('', render, format='html')) + return output + + +def talks_to_ics(): + content = 'BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:pelican\n' + for t in all_talks(): + content += talk_to_ics(t) + content += 'END:VCALENDAR\n' + return content + + +def talk_to_ics(talk): + if 'time' not in talk: + return '' + start = talk['time'] + end = start + datetime.timedelta(minutes=talk['duration']) + content = 'BEGIN:VEVENT\n' + content += "UID:%s@%d.hackmeeting.org\n" % (talk['id'], talk['day'].year) + content += "SUMMARY:%s\n" % talk['title'] + content += "DTSTAMP:%s\n" % time.strftime('%Y%m%dT%H%M%SZ', + time.gmtime(float(start.strftime('%s')))) + content += "DTSTART:%s\n" % time.strftime('%Y%m%dT%H%M%SZ', + time.gmtime(float( + start.strftime('%s')))) + content += "DTEND:%s\n" % time.strftime('%Y%m%dT%H%M%SZ', + time.gmtime(float( + end.strftime('%s')))) + content += "LOCATION:%s\n" % talk['room'] + content += 'END:VEVENT\n' + return content class TalksGenerator(generators.Generator): @@ -138,11 +275,13 @@ class TalksGenerator(generators.Generator): def generate_output(self, writer=None): for talkname in self.talks: if 'resources' in self.talks[talkname]: - outdir = os.path.join(self.output_path, 'talks', talkname, - 'res') + outdir = os.path.join(self.output_path, TALKS_PATH, talkname, + TALK_ATTACHMENT_PATH) if os.path.isdir(outdir): shutil.rmtree(outdir) shutil.copytree(self.talks[talkname]['resources'], outdir) + with open(os.path.join(self.output_path, TALK_ICS), 'w') as buf: + buf.write(talks_to_ics()) def add_talks_option_defaults(pelican): @@ -167,3 +306,4 @@ else: signals.initialized.connect(add_talks_option_defaults) directives.register_directive('talklist', TalkListDirective) directives.register_directive('talk', TalkDirective) + directives.register_directive('talkgrid', TalkGridDirective) diff --git a/talks/_talk_example/meta.yaml b/talks/_talk_example/meta.yaml index 39b6e17..c7eb090 100644 --- a/talks/_talk_example/meta.yaml +++ b/talks/_talk_example/meta.yaml @@ -1,16 +1,25 @@ # File di esempio; copialo e cambialo -title: Il titolo del talk +title: "Il titolo del talk" text: | Descrizione del talk divisa in molte righe Puoi scrivere quanto vuoi ma devi rimanere indentato Puoi anche mettere delle spaziature. -# Se ancora non è stata assegnata una stanza al talk, lasciala vuota. Non usare un valore tipo "qualunque" o + +# Se ancora non è stata assegnata una stanza al talk, commentala. Non usare un valore tipo "qualunque" o # cose del genere, che ci si incasina tutto room: antani + +# duration è la durata in minuti del talk +# duration: 50 + # Ci vanno le virgolette intorno! altrimenti 17.30 viene interpretato come un numero decimale time: "17.30" +# day è il giorno in cui avverrà il talk. Finché non decommenti il talk non sarà schedulato +# 0=giovedì, 1=venerdì, 2=sabato, 3=domenica +# day: 0 + tags: - tante - cose @@ -20,7 +29,7 @@ links: - https://git.lattuga.net/asd/foo # mail dovrebbe contenere un link alla mail con cui il talk è stato proposto # così si può sapere chi contattare e se c'è stata una discussione -mail: blabla +mail: "blabla" # Devi usare UTF-8, non t'inventare scuse, sappiamo ndo abiti # vim: set fileencoding=utf-8: diff --git a/talks/_templates/grid.html b/talks/_templates/grid.html new file mode 100644 index 0000000..aa20d46 --- /dev/null +++ b/talks/_templates/grid.html @@ -0,0 +1,39 @@ + + + + + + {% for room in rooms %} + + {% endfor %} + + + + {% for time in range (mintime, maxtime, timestep) %} + + + {% for talk in times[time / timestep * timestep] %} + {% if talk == None %} + + {% elif talk != 'skip' %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
{{room}}
{{time//60}}:{{ "%02d" % (time % 60)}} + {{talk.title}} +
+{# vim: set ft=jinja: #} diff --git a/talks/_templates/talk.html b/talks/_templates/talk.html index 55f35a4..f367379 100644 --- a/talks/_templates/talk.html +++ b/talks/_templates/talk.html @@ -10,15 +10,25 @@ {% if room is defined %}

Stanza {{ room }}

{% endif %} +{% if needs: %} +
+Materiale necessario: +{{needs|join(", ")}} +
+{% endif %} -
{{text}}
+
{{text | markdown}} + {% if contacts: %} +

A cura di {{contacts|join(', ')}}

+ {% endif %} +
{% if links is defined or resources is defined or mail is defined %}

Link utili: