summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile20
-rwxr-xr-xadmin/feeds/build-feed.py189
-rwxr-xr-xadmin/feeds/build-feeds.sh19
-rw-r--r--admin/feeds/concerts-pubdates.json15
-rwxr-xr-xbuild-concerts.py174
-rwxr-xr-xbuild-feed.sh13
-rw-r--r--en/feed.xml276
-rw-r--r--feed.xml273
-rw-r--r--helpers.py152
9 files changed, 823 insertions, 308 deletions
diff --git a/Makefile b/Makefile
index f3971d6..ca01374 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,5 @@
+#################### Variables.
+
OUTDIR = public
dirname = $(patsubst %/,%,$(dir $(1)))
@@ -32,10 +34,21 @@ fonts_folders = $(call dirnames,$(fonts))
scripts = $(foreach img,$(shell find scripts -type f),$(OUTDIR)/$(img))
scripts_folders = $(call dirnames,$(scripts))
-.PHONY: all clean site upload
+#################### Top-level targets.
+
+# Building:
+.PHONY: all clean site
+
+# Maintenance:
+.PHONY: feeds upload
+
+#################### Recipes.
all: site
+feeds:
+ ./admin/feeds/build-feeds.sh $(feeds_src)
+
upload: site
./upload.sh $(OUTDIR)
@@ -44,12 +57,9 @@ clean:
site: $(pages) $(members_pages) $(feeds) $(images) $(stylesheets) $(fonts) $(scripts)
-$(images) $(stylesheets) $(fonts) $(scripts): $(OUTDIR)/%: %
+$(images) $(stylesheets) $(fonts) $(scripts) $(feeds): $(OUTDIR)/%: %
cp $< $@
-$(feeds): $(OUTDIR)/%: %
- ./build-feed.sh $< $@
-
.SECONDEXPANSION:
# đŸ”Ș HACK ATTACK đŸ”Ș
diff --git a/admin/feeds/build-feed.py b/admin/feeds/build-feed.py
new file mode 100755
index 0000000..e010b63
--- /dev/null
+++ b/admin/feeds/build-feed.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+
+from datetime import datetime
+import json
+import re
+from sys import argv
+from urllib.parse import urljoin
+
+from lxml.builder import E
+from lxml.etree import CDATA, XML, indent, tostring
+
+from helpers import (
+ DATE_FORMATTERS,
+ guess_language,
+ read_concerts,
+ tmplocale,
+ touchup_plaintext,
+)
+
+
+# TODO: handle timezones correctly.
+# Places to disambiguate:
+#
+# - concerts.in:
+# either add the zone explicitly, or deduce it from the place,
+# assuming all times in concerts.in are local times.
+#
+# - concerts-pubdates.json:
+# just add the zone explicitly.
+#
+# Until then, assume all these "naive times" describe the same timezone
+# (CET/CEST).
+
+
+TIMEZONE = datetime.now().astimezone().tzinfo
+NOW = datetime.now(tz=TIMEZONE)
+DATE_FORMAT = '%-d %b %Y %H:%M %z'
+
+# TODO: add item pubDate
+
+
+LOCALIZED_TEXT = {
+ 'en': {
+ 'title': 'Bellefeuille Quartet',
+ 'indexpath': 'en/',
+ 'description': 'News from the Bellefeuille quartet',
+ },
+ 'fr': {
+ 'title': 'Quatuor Bellefeuille',
+ 'indexpath': '/',
+ 'description': 'Des nouvelles du quatuor Bellefeuille',
+ },
+}
+
+LOCALIZED_FORMATS = {
+ 'en': {
+ 'title': lambda c: f'{c.time.strftime("%B %-d %Y")} in {c.place}',
+ },
+ 'fr': {
+ 'title': lambda c: f'{c.time.strftime("%-d %B %Y")} Ă  {c.place}',
+ },
+}
+
+
+def join(sequence, joiner_factory):
+ # There's got to be a standard itertools/functools thingy to do that

+ result = []
+
+ for i, item in enumerate(sequence, start=1):
+ result.append(item)
+
+ if i == len(sequence):
+ break
+
+ result.append(joiner_factory())
+
+ return result
+
+
+def cdata_concert(concert, lang):
+ formatters = DATE_FORMATTERS[lang]
+
+ blocks = []
+
+ if concert.warning is not None:
+ blocks.append(E.p(concert.warning))
+
+ with tmplocale(lang):
+ blocks.extend((
+ E.p(formatters['date'](concert.time)),
+ E.p(formatters['time'](concert.time)),
+ ))
+
+ pieces = touchup_plaintext(concert.pieces)
+ instructions = touchup_plaintext(concert.instructions)
+
+ blocks.extend((
+ E.p(*join(concert.address.splitlines(), E.br)),
+ E.ol(
+ *(XML(f'<li>{line}</li>') for line in pieces.splitlines())
+ ),
+ *(XML(f'<p>{line}</p>') for line in instructions.splitlines()),
+ ))
+
+ # Do a silly dance to indent CDATA correctly.
+
+ for b in blocks:
+ indent(b)
+
+ html_blocks = (tostring(b, encoding='utf-8').decode() for b in blocks)
+
+ cdata = '\n'.join(html_blocks) + '\n'
+ cdata = re.sub('^', 8*' ', cdata, flags=re.MULTILINE)
+
+ return CDATA('\n' + cdata)
+
+
+def generate_concert(concert, concerts_url, pubdates, lang):
+ formatters = LOCALIZED_FORMATS[lang]
+
+ with tmplocale(lang):
+ title = formatters['title'](concert)
+
+ anchor = f'concert-{concert.time.strftime("%F")}'
+
+ item = E.item(
+ E.title(title),
+ E.link(f'{concerts_url}#{anchor}'),
+ E.description(cdata_concert(concert, lang)),
+ )
+
+ pubdate_str = pubdates[concert.time.isoformat(timespec='minutes')]
+
+ if pubdate_str is not None:
+ pubdate = datetime.fromisoformat(pubdate_str).replace(tzinfo=TIMEZONE)
+ item.append(E.pubDate(pubdate.strftime(DATE_FORMAT)))
+
+ return item
+
+
+def generate_concerts(concerts_src, concerts_url, concerts_pubdates, lang):
+ with open(concerts_pubdates) as pubdates_file:
+ pubdates = json.load(pubdates_file)
+
+ return tuple(
+ generate_concert(c, concerts_url, pubdates, lang)
+ for c in read_concerts(concerts_src)
+ )
+
+
+def main(concerts_src, feed_dst, concerts_pubdates, domain):
+ lang = guess_language(concerts_src)
+ text = LOCALIZED_TEXT[lang]
+
+ url = f'https://{domain}'
+ index_url = urljoin(url, text['indexpath'])
+ concerts_url = urljoin(index_url, 'concerts.html')
+
+ now_formatted = NOW.strftime(DATE_FORMAT)
+
+ concerts = generate_concerts(
+ concerts_src, concerts_url, concerts_pubdates, lang
+ )
+
+ rss = E.rss(
+ E.channel(
+ E.title(text['title']),
+ E.link(index_url),
+ E.description(text['description']),
+ E.image(
+ E.url(urljoin(url, 'images/logo.svg')),
+ E.link(concerts_url),
+ ),
+ E.lastBuildDate(now_formatted),
+ E.pubDate(now_formatted),
+ E.language(lang),
+ *concerts,
+ ),
+ version='2.0',
+ )
+
+ indent(rss)
+
+ with open(feed_dst, 'wb') as feed:
+ feed.write(tostring(rss, encoding='utf-8', xml_declaration=True))
+
+
+if __name__ == '__main__':
+ main(*argv[1:])
diff --git a/admin/feeds/build-feeds.sh b/admin/feeds/build-feeds.sh
new file mode 100755
index 0000000..99b4a6e
--- /dev/null
+++ b/admin/feeds/build-feeds.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -eu
+
+HERE=$(dirname "$0")
+ROOT=${HERE}/../..
+
+. "${ROOT}"/settings.sh
+
+FEEDS=("$@")
+
+for feed in "${FEEDS[@]}"
+do
+ concert=$(dirname "${feed}")/concerts.in
+
+ PYTHONPATH="${ROOT}" \
+ "${HERE}"/build-feed.py "${concert}" "${feed}" \
+ "${HERE}"/concerts-pubdates.json ${domain}
+done
diff --git a/admin/feeds/concerts-pubdates.json b/admin/feeds/concerts-pubdates.json
new file mode 100644
index 0000000..4232ed7
--- /dev/null
+++ b/admin/feeds/concerts-pubdates.json
@@ -0,0 +1,15 @@
+{
+ "2019-10-05T17:00": null,
+ "2019-10-06T16:00": null,
+ "2020-03-08T16:00": null,
+ "2020-08-24T20:00": null,
+ "2021-04-03T20:00": "2021-03-19T16:00",
+ "2021-06-13T15:00": "2021-03-19T16:00",
+ "2021-08-17T20:30": null,
+ "2021-08-19T20:00": null,
+ "2021-10-25T18:00": null,
+ "2021-10-28T18:00": null,
+ "2021-12-12T16:00": "2021-11-23T23:28",
+ "2021-12-31T20:00": null,
+ "2022-05-07T17:00": null
+}
diff --git a/build-concerts.py b/build-concerts.py
index 43dce57..6e99673 100755
--- a/build-concerts.py
+++ b/build-concerts.py
@@ -1,16 +1,17 @@
#!/usr/bin/env python3
-from contextlib import contextmanager
-from dataclasses import dataclass
from datetime import datetime
-import locale
-from operator import attrgetter
from pathlib import Path
-import re
from sys import argv
-from typing import Iterator, Optional
-from helpers import relative_path
+from helpers import (
+ DATE_FORMATTERS,
+ guess_language,
+ read_concerts,
+ relative_path,
+ tmplocale,
+ touchup_plaintext,
+)
# TODO: change some jargon:
@@ -18,115 +19,6 @@ from helpers import relative_path
# - canceled => warning
-LICENSE_URLS = {
- 'CC0': 'https://creativecommons.org/publicdomain/zero',
- 'CC BY': 'https://creativecommons.org/licenses/by',
- 'CC BY-SA': 'https://creativecommons.org/licenses/by-sa',
-}
-
-LICENSE_RE = re.compile(
- '('+'|'.join(LICENSE_URLS.keys())+')' + ' ([0-9.]+)'
-)
-
-
-@dataclass
-class LicenseInfo:
- tag: str
- version: str
-
- @classmethod
- def deserialize(cls, info):
- if info is None:
- return None
- return cls(*LICENSE_RE.fullmatch(info).groups())
-
- def format(self):
- url = f'{LICENSE_URLS[self.tag]}/{self.version}/'
-
- return f'<a href="{url}" target="_blank">{self.tag}</a>'
-
-
-@dataclass
-class Illustration:
- file: str
- alt_text: str
- source_name: str
- source_link: Optional[str]
- license_info: Optional[LicenseInfo]
-
- @classmethod
- def deserialize(cls, d):
- return cls(d['pic_file'],
- d['pic_alt'],
- d['pic_src'],
- d['pic_link'],
- LicenseInfo.deserialize(d['pic_license']))
-
-
-@dataclass
-class Concert:
- time: datetime
- place: str
- address: str
- pieces: Iterator[str]
- instructions: str
- illustration: Illustration
- warning: Optional[str]
-
- @classmethod
- def deserialize(cls, d):
- return cls(
- time=datetime.strptime(d['time'], '%d/%m/%Y %Hh%M'),
- place=d['place'],
- address=d['address'],
- pieces=d['pieces'],
- instructions=d['instructions'],
- illustration=Illustration.deserialize(d),
- warning=d['warning']
- )
-
-
-def optional(line):
- return f'(?:{line})?'
-
-
-CONCERT_LINES = (
- r'QUAND : (?P<time>[^\n]+)\n',
- r'O[UÙ] : (?P<place>[^\n]+)\n',
- 'ADRESSE :\n',
- '(?P<address>.+?)\n',
- 'PROGRAMME :\n',
- '(?P<pieces>.+?)\n',
- 'INSTRUCTIONS :\n',
- '(?P<instructions>.+?)\n',
- 'ILLUSTRATION :\n',
- r'fichier : (?P<pic_file>[^\n]+)\n',
- r'légende : (?P<pic_alt>[^\n]+)\n',
- r'source : (?P<pic_src>[^\n]+)\n',
- optional(r'lien : (?P<pic_link>[^\n]+)\n'),
- optional(r'licence : (?P<pic_license>[^\n]+)\n'),
- optional(r'AVERTISSEMENT : (?P<warning>[^\n]+)\n'),
-)
-
-CONCERT_RE = re.compile(''.join(CONCERT_LINES), flags=re.DOTALL)
-
-
-def guess_language(filename):
- parent = str(Path(filename).parent)
- if parent == '.':
- return 'fr'
- return parent
-
-
-def read_concerts(filename):
- with open(filename) as f:
- concerts = (
- Concert.deserialize(match)
- for match in re.finditer(CONCERT_RE, f.read())
- )
- return tuple(sorted(concerts, key=attrgetter('time')))
-
-
def split_concerts(concerts, threshold):
cutoff = len(concerts)
@@ -177,28 +69,20 @@ THUMBNAIL_TEMPLATE = '''\
'''
-@contextmanager
-def tmplocale(lang):
- old_lang, encoding = locale.getlocale()
- try:
- locale.setlocale(locale.LC_TIME, (lang, encoding))
- yield
- finally:
- locale.setlocale(locale.LC_TIME, (old_lang, encoding))
-
-
def format_credits(illustration):
- credits = illustration.source_name
+ attribution = illustration.source_name
if illustration.source_link is not None:
- credits = (f'<a href="{illustration.source_link}" target="_blank">'
- f'{illustration.source_name}'
- '</a>')
+ attribution = (
+ f'<a href="{illustration.source_link}" target="_blank">'
+ f'{illustration.source_name}'
+ '</a>'
+ )
if illustration.license_info is not None:
- credits += ' / ' + illustration.license_info.format()
+ attribution += ' / ' + illustration.license_info.format()
- return credits
+ return attribution
def format_thumbnail(concert, imgdir, lang):
@@ -256,18 +140,6 @@ DETAILS_TEMPLATE = '''\
'''
-DATE_FORMATTERS = {
- 'en': {
- 'date': lambda d: d.strftime('%A %B %-d, %Y'),
- 'time': lambda d: d.strftime('%I:%M %P'),
- },
- 'fr': {
- 'date': lambda d: d.strftime('%A %-d %B %Y').capitalize(),
- 'time': lambda d: d.strftime('%Hh%M'),
- },
-}
-
-
def detail_block(tag, classes, content):
opener = f'<{tag} class="{" ".join(classes)}">'
closer = f'</{tag}>'
@@ -286,20 +158,6 @@ def break_lines(lines):
return tuple(line+'<br>' for line in lines[:-1]) + (lines[-1],)
-TOUCHUPS = (
- (re.compile('([0-9])(st|nd|rd|th|er|Ăšre|nde|Ăšme)'), r'\1<sup>\2</sup>'),
- (re.compile('(https://[^ ]+)'), r'<a href="\1" target="_blank">\1</a>'),
- (re.compile('([^ ]+@[^ ]+)'), r'<a href="mailto:\1">\1</a>'),
-)
-
-
-def touchup_plaintext(plaintext):
- text = plaintext
- for regexp, repl in TOUCHUPS:
- text = regexp.sub(repl, text)
- return text
-
-
def print_concert_details(concert, lang):
concert_id = f'concert-{concert.time.strftime("%F")}'
classes = ('details',)
diff --git a/build-feed.sh b/build-feed.sh
deleted file mode 100755
index f537e96..0000000
--- a/build-feed.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-# TODO: someday, generate both concerts page and feeds from a
-# plaintext list of concerts.
-
-set -eu
-
-input=$1
-output=$2
-
-. settings.sh
-
-sed s,'{DOMAIN}',"${domain}", "${input}" > "${output}"
diff --git a/en/feed.xml b/en/feed.xml
index a2557e7..72d5c8e 100644
--- a/en/feed.xml
+++ b/en/feed.xml
@@ -1,87 +1,227 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='utf-8'?>
<rss version="2.0">
<channel>
<title>Bellefeuille Quartet</title>
- <link>https://{DOMAIN}</link>
+ <link>https://quatuorbellefeuille.com/en/</link>
<description>News from the Bellefeuille quartet</description>
<image>
- <url>https://{DOMAIN}/images/logo.svg</url>
- <link>https://{DOMAIN}/en/concerts.html</link>
+ <url>https://quatuorbellefeuille.com/images/logo.svg</url>
+ <link>https://quatuorbellefeuille.com/en/concerts.html</link>
</image>
+ <lastBuildDate>23 Feb 2022 20:28 +0100</lastBuildDate>
+ <pubDate>23 Feb 2022 20:28 +0100</pubDate>
<language>en</language>
<item>
- <title>December 12 2021 in Paris</title>
- <link>https://{DOMAIN}/en/concerts.html#concert-2021-12-12</link>
+ <title>October 5 2019 in Le Buisson de Cadouin</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2019-10-05</link>
+ <description><![CDATA[
+ <p>Saturday October 5, 2019</p>
+ <p>05:00 pm</p>
+ <p>PĂŽle d'Animation Culturelle<br/>Avenue d'Aquitaine<br/>24480 Le Buisson de Cadouin</p>
+ <ol>
+ <li>Mozart quartet No. 17, KV. 458, “The Hunt”</li>
+ <li>Mendelssohn quartet No. 3, Op. 44 No. 1</li>
+ <li>Ravel string quartet</li>
+ </ol>
+ <p>Information and reservation: <a href="https://arcadesinfo.com/évÚnement/quatuor-a-cordes-bellefeuille/" target="_blank">https://arcadesinfo.com/évÚnement/quatuor-a-cordes-bellefeuille/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>October 6 2019 in Lalinde</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2019-10-06</link>
+ <description><![CDATA[
+ <p>Sunday October 6, 2019</p>
+ <p>04:00 pm</p>
+ <p>Espace Jacques Brel<br/>1 rue salle des fĂȘtes<br/>24150 Lalinde</p>
+ <ol>
+ <li>Beethoven quartet Op. 18 No. 4</li>
+ <li>Chostakovitch quartet No. 11</li>
+ <li>Ravel string quartet</li>
+ </ol>
+ <p>Information and reservation: <a href="https://musiqueaucoeurdesbastides.jimdofree.com/" target="_blank">https://musiqueaucoeurdesbastides.jimdofree.com/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>March 8 2020 in Paris</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2020-03-08</link>
+ <description><![CDATA[
+ <p>Sunday March 8, 2020</p>
+ <p>04:00 pm</p>
+ <p>Chapelle Saint-Louis de la SalpĂȘtriĂšre<br/>47 boulevard de l'HĂŽpital<br/>75013 Paris</p>
+ <ol>
+ <li>Haydn quartet Op. 76 No. 1</li>
+ <li>Beethoven quartet No. 7, Op. 59 No. 1</li>
+ </ol>
+ <p>Free admission without reservation, subject to the number of available places.</p>
+ <p>For more information, please contact <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>August 24 2020 in Flaine</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2020-08-24</link>
<description><![CDATA[
- <p>Sunday December 12, 2021</p>
- <p>04:00 pm</p>
- <p>
- Chapelle Saint-Louis de la SalpĂȘtriĂšre<br>
- 47 boulevard de l'HĂŽpital<br>
- 75013 Paris
- </p>
- <ol>
- <li>Haydn 1<sup>st</sup> movement of quartet Op. 76 No. 1</li>
- <li>Schubert quartettsatz</li>
- <li>Beethoven quartet No. 7, Op. 59 No. 1</li>
- </ol>
- <p>
- Free admission without reservation, subject to the number of
- available places.
- </p>
- <p>
- For more information, please contact
- <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
- </p>
- ]]></description>
- <pubDate>Tue, 23 Nov 2021 23:28:03 +0100</pubDate>
+ <p>Monday August 24, 2020</p>
+ <p>08:00 pm</p>
+ <p>Auditorium Éric &amp; Sylvie Boissonnas<br/>Flaine ForĂȘt<br/>Route de Flaine<br/>74300 ArĂąches-la-Frasse</p>
+ <ol>
+ <li>Beethoven string trio Op. 9 No. 3</li>
+ </ol>
+ <p>Free admission without reservation, subject to the number of available places.</p>
+ <p>For more information, please contact <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
</item>
<item>
<title>April 3 2021 in Dinard</title>
- <link>https://{DOMAIN}/en/concerts.html#concert-2021-04-03</link>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-04-03</link>
<description><![CDATA[
- <p>POSTPONED</p>
- <p>Saturday April 3, 2021</p>
- <p>08:00 pm</p>
- <p>Dinard</p>
- <ol>
- <li>Haydn quartet Op. 76 No. 1</li>
- <li>Brahms quartet Op. 51 No. 2</li>
- <li>Ravel string quartet</li>
- </ol>
- <p>
- Information and reservation:
- <a href="https://weekenddemusiqueclassique.fr" target="_blank">https://weekenddemusiqueclassique.fr</a>
- </p>
- ]]></description>
- <pubDate>Fri, 19 Mar 2021 16:00:00 +0100</pubDate>
+ <p>POSTPONED</p>
+ <p>Saturday April 3, 2021</p>
+ <p>08:00 pm</p>
+ <p>Dinard</p>
+ <ol>
+ <li>Haydn quartet Op. 76 No. 1</li>
+ <li>Brahms quartet Op. 51 No. 2</li>
+ <li>Ravel string quartet</li>
+ </ol>
+ <p>Information and reservation: <a href="https://weekenddemusiqueclassique.fr" target="_blank">https://weekenddemusiqueclassique.fr</a>
+ </p>
+ ]]></description>
+ <pubDate>19 Mar 2021 16:00 +0100</pubDate>
</item>
<item>
<title>June 13 2021 in Paris</title>
- <link>https://{DOMAIN}/en/concerts.html#concert-2021-06-13</link>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-06-13</link>
+ <description><![CDATA[
+ <p>CANCELED</p>
+ <p>Sunday June 13, 2021</p>
+ <p>03:00 pm</p>
+ <p>Église Sainte-Claire d’Assise<br/>Place de la Porte-de-Pantin<br/>75019 Paris</p>
+ <ol>
+ <li>Haydn quartet Op. 76 No. 1​</li>
+ <li>Beethoven quartet No. 7, Op. 59 No. 1</li>
+ </ol>
+ <p>Free admission without reservation, subject to the number of available places.</p>
+ <p>For more information, please contact <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ <pubDate>19 Mar 2021 16:00 +0100</pubDate>
+ </item>
+ <item>
+ <title>August 17 2021 in Flaine</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-08-17</link>
+ <description><![CDATA[
+ <p>Tuesday August 17, 2021</p>
+ <p>08:30 pm</p>
+ <p>Église des Carroz<br/>Route de Flaine<br/>74300 Les Carroz-d’Arñches</p>
+ <ol>
+ <li>Ravel string quartet</li>
+ </ol>
+ <p>Free admission without reservation, subject to the number of available places.</p>
+ <p>For more information, please contact <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>August 19 2021 in Flaine</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-08-19</link>
+ <description><![CDATA[
+ <p>Thursday August 19, 2021</p>
+ <p>08:00 pm</p>
+ <p>Auditorium Éric &amp; Sylvie Boissonnas<br/>Flaine ForĂȘt<br/>Route de Flaine<br/>74300 ArĂąches-la-Frasse</p>
+ <ol>
+ <li>BartĂłk quartet No. 2</li>
+ </ol>
+ <p>Free admission without reservation, subject to the number of available places.</p>
+ <p>For more information, please contact <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>October 25 2021 in Mello</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-10-25</link>
+ <description><![CDATA[
+ <p>Monday October 25, 2021</p>
+ <p>06:00 pm</p>
+ <p>Le grand Mello<br/>Route de Creil<br/>60660 Mello</p>
+ <ol>
+ <li>Haydn quartet Op. 76 No. 1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Brahms quartet Op. 51 No. 2</li>
+ </ol>
+ <p>For more information, please refer to: <a href="https://www.ledimoredelquartetto.eu/en/october-25-2021-le-grand-mello-mello-france/" target="_blank">https://www.ledimoredelquartetto.eu/en/october-25-2021-le-grand-mello-mello-france/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>October 28 2021 in Paris</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-10-28</link>
+ <description><![CDATA[
+ <p>Thursday October 28, 2021</p>
+ <p>06:00 pm</p>
+ <p>Casa Saint Germain des Prés<br/>1 rue Madame<br/>75006 Paris</p>
+ <ol>
+ <li>Schubert quartettsatz</li>
+ <li>Brahms quartet Op. 51 No. 2</li>
+ <li>Ravel string quartet</li>
+ </ol>
+ <p>For more information, please refer to: <a href="https://www.ledimoredelquartetto.eu/en/october-28-2021-casa-st-germain-des-pres-paris/" target="_blank">https://www.ledimoredelquartetto.eu/en/october-28-2021-casa-st-germain-des-pres-paris/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>December 12 2021 in Paris</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-12-12</link>
+ <description><![CDATA[
+ <p>Sunday December 12, 2021</p>
+ <p>04:00 pm</p>
+ <p>Chapelle Saint-Louis de la SalpĂȘtriĂšre<br/>47 boulevard de l'HĂŽpital<br/>75013 Paris</p>
+ <ol>
+ <li>Haydn 1<sup>st</sup> movement of quartet Op. 76 No. 1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Beethoven quartet No. 7, Op. 59 No. 1</li>
+ </ol>
+ <p>Free admission without reservation, subject to the number of available places.</p>
+ <p>For more information, please contact <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ <pubDate>23 Nov 2021 23:28 +0100</pubDate>
+ </item>
+ <item>
+ <title>December 31 2021 in Dilbeek</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2021-12-31</link>
+ <description><![CDATA[
+ <p>Friday December 31, 2021</p>
+ <p>08:00 pm</p>
+ <p>ChĂąteau Grand-Bigard<br/>Isidoor van Beverenstraat 5<br/>1702 Dilbeek<br/>Belgium</p>
+ <ol>
+ <li>Haydn 1<sup>st</sup> movement of quartet Op. 76 No. 1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Ravel string quaret</li>
+ </ol>
+ <p>For more information, please refer to: <a href="https://www.ledimoredelquartetto.eu/en/december-31-2021-grand-bigard-castle-brussels-belgium/" target="_blank">https://www.ledimoredelquartetto.eu/en/december-31-2021-grand-bigard-castle-brussels-belgium/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>May 7 2022 in Saint-Domineuc</title>
+ <link>https://quatuorbellefeuille.com/en/concerts.html#concert-2022-05-07</link>
<description><![CDATA[
- <p>CANCELED</p>
- <p>Sunday June 13, 2021</p>
- <p>03:00 pm</p>
- <p>
- Église Sainte-Claire d’Assise<br>
- Place de la Porte-de-Pantin<br>
- 75019 Paris
- </p>
- <ol>
- <li>Haydn quartet Op. 76 No. 1​</li>
- <li>Beethoven quartet No. 7, Op. 59 No. 1</li>
- </ol>
- <p>
- Free admission without reservation, subject to the number of
- available places.
- </p>
- <p>
- For more information, please contact
- <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
- </p>
- ]]></description>
- <pubDate>Fri, 19 Mar 2021 16:00:00 +0100</pubDate>
+ <p>Saturday May 7, 2022</p>
+ <p>05:00 pm</p>
+ <p>Le Pianorium<br/>4 rue du Stade<br/>35190 Saint-Domineuc</p>
+ <ol>
+ <li>Haydn 1<sup>st</sup> movement of quartet Op. 76 No. 1</li>
+ <li>Mendelssohn quartet No. 3, Op. 44 No. 1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Shostakovich quartet</li>
+ </ol>
+ <p>Free admission, subject to the number of available places.</p>
+ <p>Contact <a href="mailto:info@pianorium.fr">info@pianorium.fr</a> to make a reservation.</p>
+ ]]></description>
</item>
</channel>
-</rss>
+</rss> \ No newline at end of file
diff --git a/feed.xml b/feed.xml
index 3842285..026e284 100644
--- a/feed.xml
+++ b/feed.xml
@@ -1,83 +1,228 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='utf-8'?>
<rss version="2.0">
<channel>
<title>Quatuor Bellefeuille</title>
- <link>https://{DOMAIN}</link>
+ <link>https://quatuorbellefeuille.com/</link>
<description>Des nouvelles du quatuor Bellefeuille</description>
<image>
- <url>https://{DOMAIN}/images/logo.svg</url>
- <link>https://{DOMAIN}/concerts.html</link>
+ <url>https://quatuorbellefeuille.com/images/logo.svg</url>
+ <link>https://quatuorbellefeuille.com/concerts.html</link>
</image>
+ <lastBuildDate>23 Feb 2022 20:27 +0100</lastBuildDate>
+ <pubDate>23 Feb 2022 20:27 +0100</pubDate>
<language>fr</language>
<item>
- <title>12 décembre 2021 à Paris</title>
- <link>https://{DOMAIN}/concerts.html#concert-2021-12-12</link>
+ <title>5 octobre 2019 Ă  Le Buisson de Cadouin</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2019-10-05</link>
+ <description><![CDATA[
+ <p>Samedi 5 octobre 2019</p>
+ <p>17h00</p>
+ <p>PĂŽle d'Animation Culturelle<br/>Avenue d'Aquitaine<br/>24480 Le Buisson de Cadouin</p>
+ <ol>
+ <li>Mozart quatuor n°17, KV. 458, « La Chasse »</li>
+ <li>Mendelssohn quatuor n°3, op.44 n°1</li>
+ <li>Ravel quatuor Ă  cordes</li>
+ </ol>
+ <p>Informations et réservations : <a href="https://arcadesinfo.com/évÚnement/quatuor-a-cordes-bellefeuille/" target="_blank">https://arcadesinfo.com/évÚnement/quatuor-a-cordes-bellefeuille/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>6 octobre 2019 Ă  Lalinde</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2019-10-06</link>
+ <description><![CDATA[
+ <p>Dimanche 6 octobre 2019</p>
+ <p>16h00</p>
+ <p>Espace Jacques Brel<br/>1 rue salle des fĂȘtes<br/>24150 Lalinde</p>
+ <ol>
+ <li>Beethoven quatuor op.18 n°4</li>
+ <li>Chostakovitch quatuor n°11</li>
+ <li>Ravel quatuor Ă  cordes</li>
+ </ol>
+ <p>Informations et réservations : <a href="https://musiqueaucoeurdesbastides.jimdofree.com/" target="_blank">https://musiqueaucoeurdesbastides.jimdofree.com/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>8 mars 2020 Ă  Paris</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2020-03-08</link>
+ <description><![CDATA[
+ <p>Dimanche 8 mars 2020</p>
+ <p>16h00</p>
+ <p>Chapelle Saint-Louis de la SalpĂȘtriĂšre<br/>47 boulevard de l'HĂŽpital<br/>75013 Paris</p>
+ <ol>
+ <li>Haydn quatuor op.76 n°1</li>
+ <li>Beethoven quatuor n°7, op.59 n°1</li>
+ </ol>
+ <p>Entrée libre sans réservation dans la limite des places disponibles.</p>
+ <p>Pour plus d'informations, merci de contacter : <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>24 août 2020 à Flaine</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2020-08-24</link>
<description><![CDATA[
- <p>Dimanche 12 décembre 2021</p>
- <p>16h00</p>
- <p>
- Chapelle Saint-Louis de la SalpĂȘtriĂšre<br>
- 47 boulevard de l'HĂŽpital<br>
- 75013 Paris
- </p>
- <ol>
- <li>Haydn 1<sup>er</sup> mouvement du quatuor op.76 n°1</li>
- <li>Schubert quartettsatz</li>
- <li>Beethoven quatuor n°7, op.59 n°1</li>
- </ol>
- <p>
- Pour plus d'informations, merci de contacter :
- <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
- </p>
- ]]></description>
- <pubDate>Tue, 23 Nov 2021 23:28:03 +0100</pubDate>
+ <p>Lundi 24 août 2020</p>
+ <p>20h00</p>
+ <p>Auditorium Éric &amp; Sylvie Boissonnas<br/>Flaine ForĂȘt<br/>Route de Flaine<br/>74300 ArĂąches-la-Frasse</p>
+ <ol>
+ <li>Beethoven trio à cordes op.9 n°3</li>
+ </ol>
+ <p>Entrée libre sans réservation, dans la limite des places disponibles.</p>
+ <p>Pour plus d'informations, merci de contacter : <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
</item>
<item>
<title>3 avril 2021 Ă  Dinard</title>
- <link>https://{DOMAIN}/concerts.html#concert-2021-04-03</link>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-04-03</link>
<description><![CDATA[
- <p>REPORTÉ</p>
- <p>Samedi 3 avril 2021</p>
- <p>20h00</p>
- <p>Dinard</p>
- <ol>
- <li>Haydn quatuor op.76 n°1</li>
- <li>Brahms quatuor op.51 n°2</li>
- <li>Ravel quatuor Ă  cordes</li>
- </ol>
- <p>
- Informations et réservations :
- <a href="https://weekenddemusiqueclassique.fr" target="_blank">https://weekenddemusiqueclassique.fr</a>
- </p>
- ]]></description>
- <pubDate>Fri, 19 Mar 2021 16:00:00 +0100</pubDate>
+ <p>REPORTÉ</p>
+ <p>Samedi 3 avril 2021</p>
+ <p>20h00</p>
+ <p>Dinard</p>
+ <ol>
+ <li>Haydn quatuor op.76 n°1</li>
+ <li>Brahms quatuor op.51 n°2</li>
+ <li>Ravel quatuor Ă  cordes</li>
+ </ol>
+ <p>Informations et réservations : <a href="https://weekenddemusiqueclassique.fr" target="_blank">https://weekenddemusiqueclassique.fr</a>
+ </p>
+ ]]></description>
+ <pubDate>19 Mar 2021 16:00 +0100</pubDate>
</item>
<item>
<title>13 juin 2021 Ă  Paris</title>
- <link>https://{DOMAIN}/concerts.html#concert-2021-06-13</link>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-06-13</link>
+ <description><![CDATA[
+ <p>ANNULÉ</p>
+ <p>Dimanche 13 juin 2021</p>
+ <p>15h00</p>
+ <p>Église Sainte-Claire d’Assise<br/>Place de la Porte-de-Pantin<br/>75019 Paris</p>
+ <ol>
+ <li>Haydn quatuor op.76 n°1​</li>
+ <li>Beethoven quatuor n°7, op.59 n°1</li>
+ </ol>
+ <p>Entrée libre sans réservation, dans la limite des places disponibles.</p>
+ <p>Pour plus d'informations, merci de contacter : <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ <pubDate>19 Mar 2021 16:00 +0100</pubDate>
+ </item>
+ <item>
+ <title>17 août 2021 à Flaine</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-08-17</link>
+ <description><![CDATA[
+ <p>Mardi 17 août 2021</p>
+ <p>20h30</p>
+ <p>Église des Carroz<br/>Route de Flaine<br/>74300 Les Carroz-d’Arñches</p>
+ <ol>
+ <li>Ravel quatuor Ă  cordes</li>
+ </ol>
+ <p>Entrée libre sans réservation, dans la limite des places disponibles.</p>
+ <p>Pour plus d'informations, merci de contacter : <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>19 août 2021 à Flaine</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-08-19</link>
+ <description><![CDATA[
+ <p>Jeudi 19 août 2021</p>
+ <p>20h00</p>
+ <p>Auditorium Éric &amp; Sylvie Boissonnas<br/>Flaine ForĂȘt<br/>Route de Flaine<br/>74300 ArĂąches-la-Frasse</p>
+ <ol>
+ <li>Bartók quatuor n°2</li>
+ </ol>
+ <p>Entrée libre sans réservation, dans la limite des places disponibles.</p>
+ <p>Pour plus d'informations, merci de contacter : <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>25 octobre 2021 Ă  Mello</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-10-25</link>
+ <description><![CDATA[
+ <p>Lundi 25 octobre 2021</p>
+ <p>18h00</p>
+ <p>Le grand Mello<br/>Route de Creil<br/>60660 Mello</p>
+ <ol>
+ <li>Haydn quatuor op.76 n°1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Brahms quatuor op.51 n°2</li>
+ </ol>
+ <p>Pour plus d'informations, rendez-vous sur : <a href="https://www.ledimoredelquartetto.eu/en/october-25-2021-le-grand-mello-mello-france/" target="_blank">https://www.ledimoredelquartetto.eu/en/october-25-2021-le-grand-mello-mello-france/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>28 octobre 2021 Ă  Paris</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-10-28</link>
+ <description><![CDATA[
+ <p>Jeudi 28 octobre 2021</p>
+ <p>18h00</p>
+ <p>Casa Saint Germain des Prés<br/>1 rue Madame<br/>75006 Paris</p>
+ <ol>
+ <li>Schubert quartettsatz</li>
+ <li>Brahms quatuor op.51 n°2</li>
+ <li>Ravel quatuor Ă  cordes</li>
+ </ol>
+ <p>Pour plus d'informations, rendez-vous sur : <a href="https://www.ledimoredelquartetto.eu/en/october-28-2021-casa-st-germain-des-pres-paris/" target="_blank">https://www.ledimoredelquartetto.eu/en/october-28-2021-casa-st-germain-des-pres-paris/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>12 décembre 2021 à Paris</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-12-12</link>
+ <description><![CDATA[
+ <p>Dimanche 12 décembre 2021</p>
+ <p>16h00</p>
+ <p>Chapelle Saint-Louis de la SalpĂȘtriĂšre<br/>47 boulevard de l'HĂŽpital<br/>75013 Paris</p>
+ <ol>
+ <li>Haydn 1<sup>er</sup> mouvement du quatuor op.76 n°1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Beethoven quatuor n°7, op.59 n°1</li>
+ </ol>
+ <p>Entrée libre sans réservation dans la limite des places disponibles.</p>
+ <p>Pour plus d'informations, merci de contacter : <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
+ </p>
+ ]]></description>
+ <pubDate>23 Nov 2021 23:28 +0100</pubDate>
+ </item>
+ <item>
+ <title>31 décembre 2021 à Dilbeek</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2021-12-31</link>
+ <description><![CDATA[
+ <p>Vendredi 31 décembre 2021</p>
+ <p>20h00</p>
+ <p>ChĂąteau Grand-Bigard<br/>Isidoor van Beverenstraat 5<br/>1702 Dilbeek<br/>Belgique</p>
+ <ol>
+ <li>Haydn 1<sup>er</sup> mouvement du quatuor op.76 n°1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Ravel quatuor Ă  cordes</li>
+ </ol>
+ <p>Pour plus d'informations, rendez-vous sur : <a href="https://www.ledimoredelquartetto.eu/en/december-31-2021-grand-bigard-castle-brussels-belgium/" target="_blank">https://www.ledimoredelquartetto.eu/en/december-31-2021-grand-bigard-castle-brussels-belgium/</a>
+ </p>
+ ]]></description>
+ </item>
+ <item>
+ <title>7 mai 2022 Ă  Saint-Domineuc</title>
+ <link>https://quatuorbellefeuille.com/concerts.html#concert-2022-05-07</link>
<description><![CDATA[
- <p>ANNULÉ</p>
- <p>Dimanche 13 juin 2021</p>
- <p>15h00</p>
- <p>
- Église Sainte-Claire d’Assise<br>
- Place de la Porte-de-Pantin<br>
- 75019 Paris
- </p>
- <ol>
- <li>Haydn quatuor op.76 n°1​</li>
- <li>Beethoven quatuor n°7, op.59 n°1</li>
- </ol>
- <p>
- Entrée libre sans réservation, dans la limite des places
- disponibles.
- </p>
- <p>
- Pour plus d'informations, merci de contacter :
- <a href="mailto:quatuorbellefeuille@gmail.com">quatuorbellefeuille@gmail.com</a>
- </p>
- ]]></description>
- <pubDate>Fri, 19 Mar 2021 16:00:00 +0100</pubDate>
+ <p>Samedi 7 mai 2022</p>
+ <p>17h00</p>
+ <p>Le Pianorium<br/>4 rue du Stade<br/>35190 Saint-Domineuc</p>
+ <ol>
+ <li>Haydn 1<sup>er</sup> mouvement du quatuor op.76 n°1</li>
+ <li>Mendelssohn quatuor n°3, op.44 n°1</li>
+ <li>Schubert quartettsatz</li>
+ <li>Chostakovitch quatuor</li>
+ </ol>
+ <p>Entrée libre dans la limite des places disponibles.</p>
+ <p>Réservation possible à l'adresse suivante : <a href="mailto:info@pianorium.fr">info@pianorium.fr</a>
+ </p>
+ ]]></description>
</item>
</channel>
-</rss>
+</rss> \ No newline at end of file
diff --git a/helpers.py b/helpers.py
index faf14e7..11a13ca 100644
--- a/helpers.py
+++ b/helpers.py
@@ -1,5 +1,19 @@
+from contextlib import contextmanager
+from dataclasses import dataclass
+from datetime import datetime
+import locale
+from operator import attrgetter
from os import path
from pathlib import Path
+import re
+from typing import Iterator, Optional
+
+
+def guess_language(filename):
+ parent = str(Path(filename).parent)
+ if parent == '.':
+ return 'fr'
+ return parent
def relative_path(*, to, ref):
@@ -7,3 +21,141 @@ def relative_path(*, to, ref):
# os.path.dirname('x') yields '' rather than '.'.
# 😼‍💹
return path.relpath(to, Path(ref).parent)
+
+
+@contextmanager
+def tmplocale(lang):
+ old_lang, encoding = locale.getlocale()
+ try:
+ locale.setlocale(locale.LC_TIME, (lang, encoding))
+ yield
+ finally:
+ locale.setlocale(locale.LC_TIME, (old_lang, encoding))
+
+
+_LICENSE_URLS = {
+ 'CC0': 'https://creativecommons.org/publicdomain/zero',
+ 'CC BY': 'https://creativecommons.org/licenses/by',
+ 'CC BY-SA': 'https://creativecommons.org/licenses/by-sa',
+}
+
+_LICENSE_RE = re.compile(
+ '('+'|'.join(_LICENSE_URLS.keys())+')' + ' ([0-9.]+)'
+)
+
+
+@dataclass
+class LicenseInfo:
+ tag: str
+ version: str
+
+ @classmethod
+ def deserialize(cls, info):
+ if info is None:
+ return None
+ return cls(*_LICENSE_RE.fullmatch(info).groups())
+
+ def format(self):
+ url = f'{_LICENSE_URLS[self.tag]}/{self.version}/'
+
+ return f'<a href="{url}" target="_blank">{self.tag}</a>'
+
+
+@dataclass
+class Illustration:
+ file: str
+ alt_text: str
+ source_name: str
+ source_link: Optional[str]
+ license_info: Optional[LicenseInfo]
+
+ @classmethod
+ def deserialize(cls, d):
+ return cls(d['pic_file'],
+ d['pic_alt'],
+ d['pic_src'],
+ d['pic_link'],
+ LicenseInfo.deserialize(d['pic_license']))
+
+
+@dataclass
+class Concert:
+ time: datetime
+ place: str
+ address: str
+ pieces: Iterator[str]
+ instructions: str
+ illustration: Illustration
+ warning: Optional[str]
+
+ @classmethod
+ def deserialize(cls, d):
+ return cls(
+ time=datetime.strptime(d['time'], '%d/%m/%Y %Hh%M'),
+ place=d['place'],
+ address=d['address'],
+ pieces=d['pieces'],
+ instructions=d['instructions'],
+ illustration=Illustration.deserialize(d),
+ warning=d['warning']
+ )
+
+
+def _optional(line):
+ return f'(?:{line})?'
+
+
+_CONCERT_LINES = (
+ r'QUAND : (?P<time>[^\n]+)\n',
+ r'O[UÙ] : (?P<place>[^\n]+)\n',
+ 'ADRESSE :\n',
+ '(?P<address>.+?)\n',
+ 'PROGRAMME :\n',
+ '(?P<pieces>.+?)\n',
+ 'INSTRUCTIONS :\n',
+ '(?P<instructions>.+?)\n',
+ 'ILLUSTRATION :\n',
+ r'fichier : (?P<pic_file>[^\n]+)\n',
+ r'légende : (?P<pic_alt>[^\n]+)\n',
+ r'source : (?P<pic_src>[^\n]+)\n',
+ _optional(r'lien : (?P<pic_link>[^\n]+)\n'),
+ _optional(r'licence : (?P<pic_license>[^\n]+)\n'),
+ _optional(r'AVERTISSEMENT : (?P<warning>[^\n]+)\n'),
+)
+
+_CONCERT_RE = re.compile(''.join(_CONCERT_LINES), flags=re.DOTALL)
+
+
+def read_concerts(filename):
+ with open(filename) as f:
+ concerts = (
+ Concert.deserialize(match)
+ for match in re.finditer(_CONCERT_RE, f.read())
+ )
+ return tuple(sorted(concerts, key=attrgetter('time')))
+
+
+_TOUCHUPS = (
+ (re.compile('([0-9])(st|nd|rd|th|er|Ăšre|nde|Ăšme)'), r'\1<sup>\2</sup>'),
+ (re.compile('(https://[^ ]+)'), r'<a href="\1" target="_blank">\1</a>'),
+ (re.compile('([^ ]+@[^ ]+)'), r'<a href="mailto:\1">\1</a>'),
+)
+
+
+def touchup_plaintext(plaintext):
+ text = plaintext
+ for regexp, repl in _TOUCHUPS:
+ text = regexp.sub(repl, text)
+ return text
+
+
+DATE_FORMATTERS = {
+ 'en': {
+ 'date': lambda d: d.strftime('%A %B %-d, %Y'),
+ 'time': lambda d: d.strftime('%I:%M %P'),
+ },
+ 'fr': {
+ 'date': lambda d: d.strftime('%A %-d %B %Y').capitalize(),
+ 'time': lambda d: d.strftime('%Hh%M'),
+ },
+}