Pārlūkot izejas kodu

ripartiamo dal sito vecchio

boyska 6 gadi atpakaļ
revīzija
b0804c62fa

+ 3 - 0
.agignore

@@ -0,0 +1,3 @@
+*/*.min.css
+*/*.min.js
+themes/*/static/*/*.min.*

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+output
+.*.sw.
+cache
+*.pid
+*.pyc
+.*.swp

+ 72 - 0
Makefile

@@ -0,0 +1,72 @@
+PY?=python
+PELICAN?=pelican
+PELICANOPTS=
+
+BASEDIR=$(CURDIR)
+INPUTDIR=$(BASEDIR)/content
+OUTPUTDIR=$(BASEDIR)/output
+CONFFILE=$(BASEDIR)/pelicanconf.py
+PUBLISHCONF=$(BASEDIR)/publishconf.py
+
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+	PELICANOPTS += -D
+endif
+VERBOSE ?= 0
+ifeq ($(VERBOSE), 1)
+	PELICANOPTS += -v
+endif
+
+all: publish
+
+help:
+	@echo 'Makefile for a pelican Web site                                        '
+	@echo '                                                                       '
+	@echo 'Usage:                                                                 '
+	@echo '   make html                        (re)generate the web site          '
+	@echo '   make clean                       remove the generated files         '
+	@echo '   make regenerate                  regenerate files upon modification '
+	@echo '   make publish                     generate using production settings '
+	@echo '   make serve [PORT=8000]           serve site at http://localhost:8000'
+	@echo '   make devserver [PORT=8000]       start/restart develop_server.sh    '
+	@echo '   make stopserver                  stop local server                  '
+	@echo '                                                                       '
+	@echo 'Set the VERBOSE variable to 1 for some more messages, e.g. make VERBOSE=1 html'
+	@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'
+	@echo '                                                                       '
+
+html:
+	$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
+
+clean:
+	[ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR)
+
+regenerate:
+	$(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
+
+serve:
+ifdef PORT
+	cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)
+else
+	cd $(OUTPUTDIR) && $(PY) -m pelican.server
+endif
+
+devserver:
+ifdef PORT
+	$(BASEDIR)/develop_server.sh restart $(PORT)
+else
+	$(BASEDIR)/develop_server.sh restart
+endif
+
+stopserver:
+	kill -9 `cat pelican.pid`
+	kill -9 `cat srv.pid`
+	@echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
+
+publish:
+	$(PELICAN) $(INPUTDIR) --ignore-cache -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
+
+autopublish:
+	while true; do inotifywait -r content pelicanconf.py publishconf.py Makefile themes -e modify -e create -e delete; make clean publish; sleep 0.1; done
+
+.PHONY: html help clean regenerate serve devserver publish 

+ 34 - 0
README.md

@@ -0,0 +1,34 @@
+Hackmeeting 2018
+==================
+
+Sources for Italian Hackmeeting 0x15 (2018) website.
+
+
+HowTo
+-------
+
+So you want to contribute, nice!
+
+```
+mkvirtualenv hackmeeting-website
+pip install -r requirements.txt
+make
+firefox output/index.html
+```
+
+Also, `make help` is your friend.
+
+**Morte ai nemici dell'UTF-8**
+
+Se devi debuggare, `make DEBUG=1`
+
+Aggiungere un talk
+--------------------
+
+```
+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!

+ 6 - 0
content/pages/come_arrivare.en.md

@@ -0,0 +1,6 @@
+Title: Where
+slug: come-arrivare
+navbar_sort: 2
+lang: en
+
+##  LSOA Buridda, Genova

+ 6 - 0
content/pages/come_arrivare.md

@@ -0,0 +1,6 @@
+Title: Come arrivare
+slug: come-arrivare
+navbar_sort: 2
+
+##  LSOA Buridda, Genova
+

+ 17 - 0
content/pages/contatti.en.rst

@@ -0,0 +1,17 @@
+Contact
+###########
+
+:slug: contact
+:navbar_sort: 8
+:lang: en
+
+**Mailing List**
+
+There is a `mailing list <https://www.autistici.org/mailman/listinfo/hackmeeting>`_ where you can ask for info and follow the discussions about the meeting. It is mostly in italian but feel free to ask questions in english.
+
+**IRC**
+
+There is also an IRC (Internet Relay Chat) channel where discuss and chat with other participants: connect to server ``irc.autistici.org`` and join channel ``#hackit99`` (again, it will be mostly in italian, but english speakers are welcome).
+
+
+

+ 21 - 0
content/pages/contatti.rst

@@ -0,0 +1,21 @@
+Contatti
+###########
+
+:slug: contact
+:navbar_sort: 8
+:lang: it
+
+**Mailing List**
+
+La comunità Hackmeeting ha una `lista di discussione <https://www.autistici.org/mailman/listinfo/hackmeeting>`_ dove poter chiedere informazioni e seguire le attività della comunità.
+
+**IRC**
+
+Esiste anche un canale IRC (Internet Relay Chat) dove poter discutere e chiacchierare con tutti i membri della comunità: collegati al server ``irc.autistici.org`` ed entra nel canale ``#hackit99``.
+
+..  **Mailing List locale**
+    Per facilitare l'organizzazione dell Hackmeeting è stata creata una mailing list locale, se sei in zona e vuoi contribuire o semplicemente vuoi seguire da vicino l'organizzazione puoi iscriverti ad
+
+
+
+

+ 16 - 0
content/pages/index.en.rst

@@ -0,0 +1,16 @@
+About
+#####
+
+:navbar_sort: 1
+:lang: en
+:slug: index
+
+July 6-8, 2018 / Val di Susa / Turin
+========================================
+
+ Hackmeeting is the yearly Italian digital counter-cultures meeting; it gathers those communities that take a hard look at how technologies work in our society. And that's not all. We tell you, just you, in a whisper (don't even tell anybody!): Hack-it is just for real hackers, that is to say for those people who want to manage their own lives as they want and are ready to fight for this right, even though they haven't ever seen a computer in their life.
+
+ Three days of lessons, games, parties, debates, crossfires and collective learning, analyzing together those technologies that we use everyday, the way they change and how they can impact on our real or virtual lives; which role we can play in order to redirect these changes and set us free of control from those who want to monopolize their development, letting society crumble and relegating us in even tighter virtual spaces.
+
+ **The event is totally self-managed: there are neither promoters nor users, just participants.**
+

+ 16 - 0
content/pages/index.rst

@@ -0,0 +1,16 @@
+About
+#####
+
+:navbar_sort: 1
+:lang: it
+:slug: index
+
+6-8 Luglio 2018 / Genova
+========================================
+
+ L'*hackmeeting* è l'incontro annuale delle controculture digitali italiane, di quelle comunità che si pongono in maniera critica rispetto ai meccanismi di sviluppo delle tecnologie all'interno della nostra società. Ma non solo, molto di più. Lo sussuriamo nel tuo orecchio e soltanto nel tuo, non devi dirlo a nessuno: l'hackit è solo per veri hackers, ovvero per chi vuole gestirsi la vita come preferisce e sa s/battersi per farlo. Anche se non ha mai visto un computer in vita sua.
+
+ Tre giorni di seminari, giochi, feste, dibattiti, scambi di idee e apprendimento collettivo, per analizzare assieme le tecnologie che utilizziamo quotidianamente, come cambiano e che stravolgimenti inducono sulle nostre vite reali e virtuali, quale ruolo possiamo rivestire nell'indirizzare questo cambiamento per liberarlo dal controllo di chi vuole monopolizzarne lo sviluppo, sgretolando i tessuti sociali e relegandoci in spazi virtuali sempre più stretti.
+
+ **L'evento è totalmente autogestito: non ci sono organizzatori e fruitori, ma solo partecipanti.**
+

+ 99 - 0
content/pages/info.en.md

@@ -0,0 +1,99 @@
+Title: Info
+slug: info
+navbar_sort: 1
+lang: en
+
+## Info
+
+
+* **Who is organizing?**  
+Hackmeeting is a yearly meeting of a community that communicates
+through
+[a mailing list](https://www.autistici.org/mailman/listinfo/hackmeeting). There
+is no distinction between organizers and users. Everyone can subscribe
+and partecipate in the organization by visiting the site
+[it.hackmeeting.org](https://it.hackmeeting.org) and entering the community.
+
+* **What is an hacker?**  
+Hackers are curious people, always eager to discover how things are
+done. Whether it is technology or not, hackers reclaim freedom to
+experiment, disassemble and reassemble things or concepts, to
+understand how are they made, to improve them, and then to share how
+to do it again. Hackers solve problems and build things, believing in
+freedom and sharing. They do not like closed systems. Hackers' forma
+mentis is not restricted to the field of software hacking: there are
+people that keep the hacker mentality in every existing field, driven
+by their creative impulse.
+
+* **Who holds the talks?**  
+Whoever wants to. If someone wants to propose a talk, he just has to
+propose it on the mailing list. If the proposal is well received, it
+gets on calendar. If there are some problems, the community will be
+happy to help improve the proposal.
+
+* **What’s in there, besides talks?**  
+There is a LAN space, as to say an area dedicated to the net: everyone
+can plug their laptop, forming a network with the other participants. In
+general, this is the right place to meet other attendees, to ask for
+help in installing Linux, to solve a doubt, or just to have a chat.
+Hackmeeting is an open-air festival, a meeting, an hacking party, a
+moment of consideration, an occasion to learn something together, an act
+of rebellion, an exhange of ideas, experiences, dreams, utopias.
+
+* **How much does it costs?**  
+Traditionally, entrance in Hackmeeting is totally free: always keep in
+mind that organizing the event has a cost. Expenses are sustained
+through voluntary contributions, selling shirts and other gadgets and
+sometimes through the earnings of the bar.  
+Please donate whatever you can, every small donation counts.
+
+* **Sleeping**  
+Hackmeeting will be held in a large open green area, so it is
+**necessary** to bring tends and sleeping bags for camping. You can
+park your caravan or camper-van not too far away.  
+There will **no** place to sleep inside, plan accordingly!
+
+* **Eating**  
+There is a self-organized kitchen, and we invite everyone to
+participate. As for every year, alimentary restrictions of every type
+will be respected, so there will be vegetarian and vegan meals.
+We also try to respect any other necessity (gluten-free meals and so
+on), if this is your case please write it in
+the mailing list.  
+You should bring your own crockery/plates and cutlery (you are going
+camping!). We have a few in place but they will not be enough for everybody.
+
+* **What shall I bring?**  
+If you want to bring a computer, take a power
+strip too. Do not forget networking hardware (like Ethernet cables,
+switches, WiFi access points). Remember to bring the hardware that
+you will want to hack in company. We will try to get some internet
+connection for everybody to share, but we can't really guarantee anything.
+If you think that you need it, bring a 3G/4G stick with you
+and the necessary to share it with friends! Try to be as independent
+as possible regarding your hardware.
+
+* **May I arrive before Thursday?**  
+Do you want to arrive sooner? Wonderful! In the preceding days there is
+always much to do (prepare web infrastructure, prepare seminar halls and
+many other things) so any helping hand is well accepted.
+Maybe just let us know in advance by sending a message in the list!
+
+* **May I take photos, make videos, post, tag, share, upload?**  
+We think that the freedom to choose the dimensions of one’s own private
+sphere and public profile must be guaranteed to every participant: in
+this spirit, photos and/or videos are admitted only if explicitly
+authorized by every person that appears in the media. Nobody should be
+photographed without knowing.  
+Many people at Hackmeeting really care about their privacy, please ask
+before taking a picture.
+
+* **How are people expected to behave?**  
+Hackmeeting is a self-managed space, a temporary independent space and
+whoever passes through is expected to behave according to the principles
+of antisexism, antiracism and antifascism. If you are victim or witness
+of an act of oppression, aggression, brute force, port scan, ping flood
+and other non-consensual DOS and you do not know how to react, always
+count on the community’s support and do not hesitate to attract
+attention and ask for help.
+

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 44 - 0
content/pages/info.md


+ 29 - 0
content/pages/programma.en.rst

@@ -0,0 +1,29 @@
+Schedule
+===========
+
+:slug: schedule
+:navbar_sort: 7
+:lang: en
+
+
+`Add the schedule <schedule.ics>`_ as a calendar
+
+The schedule is still work in progress: a large part of hackmeeting
+contents are scheduled last-minute!
+
+Read the `call for contents (Italian) <http://lists.autistici.org/message/20170502.165954.0e930b75.en.html>`_ and propose yours in `mailing list <{filename}contatti.rst>`_.
+
+
+Contents in a language other than Italian are not only accepted, but
+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::
+    :lang: en
+
+.. talklist::
+    :lang: en
+
+

+ 23 - 0
content/pages/programma.rst

@@ -0,0 +1,23 @@
+Programma
+===========
+
+:slug: schedule
+:navbar_sort: 7
+:lang: it
+
+`Aggiungi il programma <schedule.ics>`_ di hackmeeting nel tuo calendario.
+
+Il programma per ora è solo orientativo: molti dei contenuti vengono proposti all'ultimo minuto!
+
+Leggi l'`invito a presentare dei contenuti
+<http://lists.autistici.org/message/20170502.165954.0e930b75.en.html>`_, fatti coraggio e proponi il tuo contenuto in `mailing
+list <{filename}contatti.rst>`_
+
+`Ascolta gli audio <https://hackmeeting.org/media/hackit17/>`_.
+
+.. talkgrid::
+    :lang: it
+
+.. talklist::
+    :lang: it
+

+ 55 - 0
content/pages/stampa.rst

@@ -0,0 +1,55 @@
+Stampa
+#########
+
+:slug: press
+:navbar_sort: 10
+:lang: it
+
+.. contents:: local
+
+Propaganda
+=================
+
+Foto
+==========
+
+.. image:: images/press/2016-IMG_0578.jpg
+    :width: 300px
+    :height: 200px
+    :alt: La bacheca dei seminari dell'hackmeeting 2016, a Pisa
+    :target: https://hackmeeting.org/hackit16/images/photos/IMG_0578.jpg
+
+.. image:: images/press/2016-IMG_0581.jpg
+    :width: 300px
+    :height: 171px
+    :alt: Sessione di elettronica digitale
+    :target: https://hackmeeting.org/hackit16/images/photos/IMG_0581.jpg
+
+.. image:: images/press/2016-IMG_0584.jpg
+    :width: 300px
+    :height: 200px
+    :alt: Programmazione
+    :target: https://hackmeeting.org/hackit16/images/photos/IMG_0584.jpg
+
+.. image:: images/press/2016-IMG_0586.jpg
+    :width: 300px
+    :height: 200px
+    :alt: Computer
+    :target: https://hackmeeting.org/hackit16/images/photos/IMG_0586.jpg
+
+.. image:: images/press/2016-IMG_0589.jpg
+    :width: 300px
+    :height: 200px
+    :alt: Il LAN party: un posto dove sperimentare insieme
+    :target: https://hackmeeting.org/hackit16/images/photos/IMG_0589.jpg
+
+.. image:: images/press/2016-IMG_0574.jpg
+    :width: 200px
+    :height: 300px
+    :alt: Un hack su Emilio: il famoso robottino degli anni '90 è stato riprogrammato
+    :target: https://hackmeeting.org/hackit16/images/photos/IMG_0574.jpg
+
+
+Comunicati stampa
+========================
+

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 165 - 0
content/pages/storia.md


+ 29 - 0
content/pages/warmup.rst

@@ -0,0 +1,29 @@
+:slug: warmup
+:navbar_sort: 4
+:lang: it
+:title: WarmUp
+
+Warmup
+###########
+
+Cosa sono
+--------------
+
+I warmup sono eventi "preparatori" ad hackmeeting. Avvengono in giro per l'Italia, e possono trattare gli
+argomenti più disparati.
+
+
+Elenco
+---------
+.. contents:: :local:
+
+
+
+Proporre un warmup
+------------------
+
+Vuoi fare un warmup? ottimo!
+
+* iscriviti alla [mailing list di hackmeeting](https://www.autistici.org/mailman/listinfo/hackmeeting).
+* scrivi in mailing list riguardo al tuo warmup: non c'è bisogno di alcuna "approvazione ufficiale", ma segnalarlo in lista è comunque un passaggio utile per favorire dibattito e comunicazione.
+

+ 75 - 0
pelicanconf.py

@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+AUTHOR = u'Hackmeeting'
+SITENAME = u'Hackmeeting 0x15'
+CC_LICENSE = 'by-nc-sa'
+SITEURL = 'https://hackmeeting.org/hackit18/'
+
+PATH = 'content'
+PAGE_PATHS = ['pages']
+ARTICLE_PATHS = ['news']
+STATIC_PATHS = ['images', 'talks', 'extra']
+
+TIMEZONE = 'Europe/Paris'
+
+DEFAULT_LANG = u'it'
+
+# Feed generation is usually not desired when developing
+FEED_ALL_ATOM = None
+CATEGORY_FEED_ATOM = None
+TRANSLATION_FEED_ATOM = None
+AUTHOR_FEED_ATOM = None
+AUTHOR_FEED_RSS = None
+
+# Blogroll
+LINKS = None
+# Social widget
+SOCIAL = None
+DEFAULT_PAGINATION = 10
+USE_OPEN_GRAPH = False  # COL CAZZO 
+
+# Uncomment following line if you want document-relative URLs when developing
+# RELATIVE_URLS = True
+
+DEFAULT_DATE = (2016, 3, 1)
+TYPOGRIFY = True
+
+PAGE_ORDER_BY = 'navbar_sort'
+PAGE_URL = '{slug}.html'
+PAGE_SAVE_AS = '{slug}.html'
+PAGE_LANG_URL = '{slug}.{lang}.html'
+PAGE_LANG_SAVE_AS = '{slug}.{lang}.html'
+INDEX_SAVE_AS = 'articles.html'
+
+
+# PAGE_BACKGROUND = 'images/background.jpg'
+THEME = 'themes/hackit0x14/'
+FONT_URL = 'theme/css/anaheim.css'
+
+# Custom css by sticazzi.
+CUSTOM_CSS = 'theme/css/hackit.css' 
+EXTRA_PATH_METADATA = {
+        # 'extra/main.css': {'path': 'themes/pelican-bootstrap3/static/css/main.css' },
+        'extra/favicon.png': {'path': 'images/favicon.png'}
+}
+
+# Pelican bootstrap 3 theme settings
+BOOTSTRAP_THEME = 'darkly'
+
+HIDE_SIDEBAR = True
+PLUGIN_PATHS = ['plugins']
+PLUGINS = ['langmenu', 'talks']
+
+MD_EXTENSIONS = ['toc']
+
+#TALKS = {
+#    u'1': { u'Speaker' : u'',
+#        u'Title': u'',
+#         u'Abstract': u'',
+#         u'Room': u'',
+#         u'Schedule': u'',
+#         u'Day':'',
+#    }
+#}

+ 39 - 0
plugins/langmenu.py

@@ -0,0 +1,39 @@
+'''
+This plugin attemps to create something similar to menuitems,
+but more meaningful with respect to l10n
+'''
+from __future__ import print_function
+
+from pelican import signals
+
+
+def add_localmenuitems(generator):
+    menu = {}  # lang: list of pages
+    for page in generator.context['pages']:
+        menu.setdefault(page.lang, [])
+        for tr in page.translations:
+            menu.setdefault(tr.lang, [])
+    print('we have langs ' + ','.join(menu.keys()))
+    for page in sorted(generator.context['pages'],
+                       key=lambda x: int(x.navbar_sort)):
+        defined_langs = []
+        menu[page.lang].append(page)
+        defined_langs.append(page.lang)
+        for tr in page.translations:
+            menu[tr.lang].append(tr)
+            defined_langs.append(tr.lang)
+        for lang in menu.keys():
+            if lang not in defined_langs:
+                menu[lang].append(page)
+
+    menuitems = {}
+    for lang in menu:
+        menuitems[lang] = []
+        for page in menu[lang]:
+            menuitems[lang].append((page.title, page.url))
+
+    generator.context['LOCALMENUITEMS'] = menuitems
+
+
+def register():
+    signals.page_generator_finalized.connect(add_localmenuitems)

+ 394 - 0
plugins/talks.py

@@ -0,0 +1,394 @@
+'''
+Manage talks scheduling in a semantic way
+'''
+
+
+from __future__ import print_function
+import os
+import io
+from functools import wraps
+import logging
+import re
+import datetime
+import shutil
+import time
+from copy import copy
+import locale
+from contextlib import contextmanager
+from babel.dates import format_date, format_datetime, format_time
+
+import markdown
+from docutils import nodes
+from docutils.parsers.rst import directives, Directive
+
+from pelican import signals, generators
+import jinja2
+
+pelican = None  # This will be set during register()
+
+
+
+
+def memoize(function):
+    '''decorators to cache'''
+    memo = {}
+
+    @wraps(function)
+    def wrapper(*args):
+        if args in memo:
+            return memo[args]
+        else:
+            rv = function(*args)
+            memo[args] = rv
+            return rv
+    return wrapper
+
+
+@contextmanager
+def setlocale(name):
+    saved = locale.setlocale(locale.LC_ALL)
+    try:
+        yield locale.setlocale(locale.LC_ALL, name)
+    finally:
+        locale.setlocale(locale.LC_ALL, saved)
+
+
+@memoize
+def get_talk_names():
+    return [name for name in os.listdir(pelican.settings['TALKS_PATH'])
+            if not name.startswith('_') and
+            get_talk_data(name) is not None
+            ]
+
+
+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(pelican.settings['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(pelican.settings['TALKS_PATH'], talkname, 'meta.yaml')
+    if not os.path.isfile(fname):
+        return None
+    with io.open(fname, encoding='utf8') as buf:
+        try:
+            data = yaml.load(buf)
+        except:
+            logging.exception("Syntax error reading %s; skipping", fname)
+            return None
+    if data is None:
+        return None
+    try:
+        gridstep = pelican.settings['TALKS_GRID_STEP']
+        if 'title' not in data:
+            logging.warn("Talk <{}> has no `title` field".format(talkname))
+            data['title'] = talkname
+        if 'text' not in data:
+            logging.warn("Talk <{}> has no `text` field".format(talkname))
+            data['text'] = ''
+        if 'duration' not in data:
+            logging.info("Talk <{}> has no `duration` field (50min used)"
+                         .format(talkname))
+            data['duration'] = 50
+        data['duration'] = int(data['duration'])
+        if data['duration'] < gridstep:
+            logging.info("Talk <{}> lasts only {} minutes; changing to {}"
+                         .format(talkname, data['duration'], gridstep))
+            data['duration'] = gridstep
+        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:
+                del data['time']
+            if 'day' in data:
+                del data['day']
+        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.datetime.combine(
+                    data['day'], datetime.time(*timeparts))
+            else:
+                logging.error("Talk <%s> has malformed `time`", talkname)
+        data['id'] = talkname
+        resdir = os.path.join(pelican.settings['TALKS_PATH'], talkname,
+                              pelican.settings['TALKS_ATTACHMENT_PATH'])
+        if os.path.isdir(resdir) and os.listdir(resdir):
+            data['resources'] = resdir
+        return data
+    except:
+        logging.exception("Error on talk %s", talkname)
+        raise
+
+
+@memoize
+def jinja_env():
+    env = jinja2.Environment(
+        loader=jinja2.FileSystemLoader(os.path.join(pelican.settings['TALKS_PATH'], '_templates')),
+        autoescape=True,
+    )
+    env.filters['markdown'] = lambda text: \
+            jinja2.Markup(markdown.Markdown(extensions=['meta']).
+                          convert(text))
+    env.filters['dateformat'] = format_date
+    env.filters['datetimeformat'] = format_datetime
+    env.filters['timeformat'] = format_time
+    return env
+
+
+class TalkListDirective(Directive):
+    final_argument_whitespace = True
+    has_content = True
+    option_spec = {
+        'lang': directives.unchanged
+    }
+
+    def run(self):
+        lang = self.options.get('lang', 'C')
+        tmpl = jinja_env().get_template('talk.html')
+
+        def _sort_date(name):
+            '''
+            This function is a helper to sort talks by start date
+
+            When no date is available, put at the beginning
+            '''
+            d = get_talk_data(name)
+            if 'time' in d:
+                return d['time']
+            return datetime.datetime(1, 1, 1)
+
+        return [
+            nodes.raw('', tmpl.render(lang=lang, **get_talk_data(n)),
+                      format='html')
+            for n in sorted(get_talk_names(),
+                            key=_sort_date)
+                                                                ]
+
+
+class TalkDirective(Directive):
+    required_arguments = 1
+    final_argument_whitespace = True
+    has_content = True
+    option_spec = {
+        'lang': directives.unchanged
+    }
+
+    def run(self):
+        lang = self.options.get('lang', 'C')
+        tmpl = jinja_env().get_template('talk.html')
+        data = get_talk_data(self.arguments[0])
+        if data is None:
+            return []
+        return [
+            nodes.raw('', tmpl.render(lang=lang, **data),
+                      format='html')
+        ]
+
+
+class TalkGridDirective(Directive):
+    '''A complete grid'''
+    final_argument_whitespace = True
+    has_content = True
+    option_spec = {
+        'lang': directives.unchanged
+    }
+
+    def run(self):
+        lang = self.options.get('lang', 'C')
+        tmpl = jinja_env().get_template('grid.html')
+        output = []
+        days = unique_attr(all_talks(), 'day')
+        gridstep = pelican.settings['TALKS_GRID_STEP']
+        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 = set()
+            for t in talks:
+                if type(t['room']) is list:
+                    for r in t['room']:
+                        rooms.add(r)
+                else:
+                    rooms.add(t['room'])
+            rooms = list(sorted(rooms))
+            # room=* is not a real room.
+            # Remove it unless that day only has special rooms
+            if '*' in rooms and len(rooms) > 1:
+                del rooms[rooms.index('*')]
+            mintime = min({talk['time'].hour * 60 +
+                           talk['time'].minute
+                           for talk in talks}) // gridstep * gridstep
+            maxtime = max({talk['time'].hour * 60 +
+                           talk['time'].minute +
+                           talk['duration']
+                           for talk in talks})
+            times = {}
+
+            for t in range(mintime, maxtime, gridstep):
+                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 // gridstep * gridstep  # round
+                assert position in times
+                if talk['room'] == '*':
+                    roomnums = range(len(rooms))
+                elif type(talk['room']) is list:
+                    roomnums = [rooms.index(r) for r in talk['room']]
+                else:
+                    roomnums = [rooms.index(talk['room'])]
+                for roomnum in roomnums:
+                    if times[position][roomnum] is not None:
+                        logging.error("Talk %s and %s overlap! (room %s)",
+                                      times[position][roomnum]['id'],
+                                      talk['id'],
+                                      rooms[roomnum]
+                                      )
+                        continue
+                    times[position][roomnum] = copy(talk)
+                    times[position][roomnum]['skip'] = False
+                    for i in range(1, talk['duration'] // gridstep):
+                        times[position + i*gridstep][roomnum] = copy(talk)
+                        times[position + i*gridstep][roomnum]['skip'] = True
+
+            render = tmpl.render(times=times,
+                                 rooms=rooms,
+                                 mintime=mintime, maxtime=maxtime,
+                                 timestep=gridstep,
+                                 lang=lang,
+                                 )
+            output.append(nodes.raw(
+                '', u'<h4>%s</h4>' % format_date(day, format='full',
+                                                 locale=lang),
+                format='html'))
+            output.append(nodes.raw('', render, format='html'))
+        return output
+
+
+def talks_to_ics():
+    content = u'BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:pelican\n'
+    for t in all_talks():
+        try:
+            content += talk_to_ics(t)
+        except:
+            logging.exception("Error producing calendar for talk %s", t['id'])
+    content += 'END:VCALENDAR\n'
+    return content
+
+
+def talk_to_ics(talk):
+    if 'time' not in talk or 'duration' 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'] if 'room' in talk else 'todo')
+    content += 'END:VEVENT\n'
+    return content
+
+
+class TalksGenerator(generators.Generator):
+    def __init__(self, *args, **kwargs):
+        self.talks = []
+        super(TalksGenerator, self).__init__(*args, **kwargs)
+
+    def generate_context(self):
+        self.talks = {n: get_talk_data(n) for n in get_talk_names()}
+        self._update_context(('talks',))
+
+    def generate_output(self, writer=None):
+        for talkname in self.talks:
+            if 'resources' in self.talks[talkname]:
+                outdir = os.path.join(self.output_path,
+                                      pelican.settings['TALKS_PATH'], talkname,
+                                      pelican.settings['TALKS_ATTACHMENT_PATH'])
+                if os.path.isdir(outdir):
+                    shutil.rmtree(outdir)
+                shutil.copytree(self.talks[talkname]['resources'], outdir)
+        with io.open(os.path.join(self.output_path, pelican.settings.get('TALKS_ICS')),
+                     'w',
+                     encoding='utf8') as buf:
+            buf.write(talks_to_ics())
+
+
+def add_talks_option_defaults(pelican):
+    pelican.settings.setdefault('TALKS_PATH', 'talks')
+    pelican.settings.setdefault('TALKS_ATTACHMENT_PATH', 'res')
+    pelican.settings.setdefault('TALKS_ICS', 'schedule.ics')
+    pelican.settings.setdefault('TALKS_GRID_STEP', 30)
+
+
+def get_generators(gen):
+    return TalksGenerator
+
+
+def pelican_init(pelicanobj):
+    global pelican
+    pelican = pelicanobj
+
+try:
+    import yaml
+except ImportError:
+    print('ERROR: yaml not found. Talks plugins will be disabled')
+
+    def register():
+        pass
+else:
+
+    def register():
+        signals.initialized.connect(pelican_init)
+        signals.get_generators.connect(get_generators)
+        signals.initialized.connect(add_talks_option_defaults)
+        directives.register_directive('talklist', TalkListDirective)
+        directives.register_directive('talk', TalkDirective)
+        directives.register_directive('talkgrid', TalkGridDirective)

+ 22 - 0
publishconf.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+# This file is only used if you use `make publish` or
+# explicitly specify it as your config file.
+
+import os
+import sys
+sys.path.append(os.curdir)
+from pelicanconf import *
+
+SITEURL = '/hackit17'
+RELATIVE_URLS = True
+TALKS_GRID_STEP = 30
+
+# DELETE_OUTPUT_DIRECTORY = True
+
+# Following items are often useful when publishing
+
+#DISQUS_SITENAME = ""
+#GOOGLE_ANALYTICS = ""

+ 16 - 0
requirements.txt

@@ -0,0 +1,16 @@
+Babel==2.4.0
+blinker==1.3
+docutils==0.12
+feedgenerator==1.7
+Jinja2==2.7.3
+Markdown==2.6.1
+MarkupSafe==0.23
+pelican==3.5.0
+Pygments==2.0.2
+python-dateutil==2.4.1
+pytz==2014.10
+PyYAML==3.12
+six==1.9.0
+smartypants==1.8.6
+typogrify==2.0.7
+Unidecode==0.4.17

+ 4 - 0
talks/README.md

@@ -0,0 +1,4 @@
+Per aggiungere un talk, copia la directory `_talk_example` in una nuova
+directory che contenga **solo caratteri alfabetici**, quindi
+cambia il file ``meta.yaml`` a tuo piacimento.
+Usa UTF-8 o morirai.

+ 36 - 0
talks/_templates/grid.html

@@ -0,0 +1,36 @@
+<table class="talk-grid rooms-{{-rooms|length}}">
+    <thead>
+        <tr>
+            <th></th>
+            {% for room in rooms %}
+                <th>{{room}}</th>
+            {% endfor %}
+        </tr>
+    </thead>
+    <tbody>
+        {% for time in range (mintime, maxtime, timestep) %}
+        <tr>
+            <td>{{time//60}}:{{ "%02d" % (time % 60)}}</td>
+            {% for talk in times[time / timestep * timestep] %}
+                {% if not loop.first and talk.room == '*' %}
+                    {# skip: covered by colspan #}
+                {% elif talk == None %}
+                    <td></td>
+                {% elif not talk.skip %}
+                    <td id="t-cell-{{talk.id}}"  class="talk
+                    {% if talk.room == '*' -%}allrooms{%-endif-%}
+                    {% for t in talk.tags -%} tag-{{t|replace(' ', '_')}} {%endfor-%}
+                    "
+                        rowspan="{{talk.duration // timestep}}"
+                        {% if talk.room == '*' %}colspan="{{rooms|length}}"{%endif%}>
+                        <a href="#talk-{{talk.id}}"
+                           title="{{talk.tags|join(",")}}"
+                           >{{talk.title}}</a>
+                    </td>
+                {% endif %}
+        {% endfor %}
+        </tr>
+        {% endfor %}
+    </tbody>
+</table>
+{# vim: set ft=jinja: #}

+ 57 - 0
talks/_templates/talk.html

@@ -0,0 +1,57 @@
+<div id="talk-{{id}}"
+     class="{% for t in tags -%} tag-{{t|replace(' ', '_')}} {% endfor -%}">
+    <h3 class="talk-title">{{title}} <a href="#t-cell-{{id}}">[top]</a></h3>
+    <div class="talk-info">
+        <p>
+        {% if time is defined and day is defined %}
+            {# Vedi http://babel.pocoo.org/en/latest/dates.html #}
+            <span>
+                {{day|dateformat(format='EEEE', locale=lang)}}
+                -
+                {{time.time()|timeformat(format='short', locale=lang)}}
+                {% if duration %} (
+                {%- if duration >= 60 -%}{{duration//60}}h{%- endif -%}
+            {%- if duration % 60 != 0 -%}{{duration%60}}m {%- endif -%})
+        {% endif %}
+            </span>
+    {% else %}
+        <i>L'orario non è ancora stato fissato</i>
+    {% endif %} {# date-time #}
+    {% if room is defined %}
+        <span>Stanza {{ room }}</span>
+    {% endif %}
+    </p>
+
+    {% if needs: %}
+<div class="talk-needs">
+<strong>Materiale necessario:</strong>
+{{needs|join(", ")}}
+</div>
+{% endif %}
+    </div>
+    <div class="talk-description">{{text | markdown}}
+            {% if contacts: %}
+                <p class="contacts">A cura di {{contacts|join(', ')}}</p>
+            {% endif %}
+</div>
+    {% if links or resources or mail: %}
+    <div class="talk-resources">
+        <h4>Link utili:</h4>
+        <ul>
+            {% if links is defined: %}
+                {% for link in links %}
+                    <li>{{link|urlize}}</li>
+                {% endfor %}
+            {% endif %}
+            {% if resources is defined: %}
+                <li><a href="{{resources}}">Materiali</a></li>
+            {% endif %}
+            {% if mail is defined: %}
+                <li><a href="{{mail}}">Mail</a></li>
+            {% endif %}
+        </ul>
+    </div>
+    {% endif %}
+</div>
+
+{# vim: set ft=jinja: #}

+ 1 - 0
talks/meta.yaml

@@ -0,0 +1 @@
+startdate: 2018-08-06