diff options
| author | Kévin Le Gouguec <kevin.legouguec@gmail.com> | 2022-03-20 15:28:54 +0100 |
|---|---|---|
| committer | Kévin Le Gouguec <kevin.legouguec@gmail.com> | 2022-03-20 15:28:54 +0100 |
| commit | d557e77493201690f880f67a209ab07e95cd16bb (patch) | |
| tree | 9f528b27708f510327f4ad45ec7926178d0c79e4 | |
| parent | 368daac4cd6a33352d92eddfe656e2f357275c8b (diff) | |
| parent | a3b76e82d935d78e41aa54c3228dbd6fb9e36fc1 (diff) | |
| download | quatuorbellefeuille.com-d557e77493201690f880f67a209ab07e95cd16bb.tar.xz | |
Merge branch 'build-feed'
| -rw-r--r-- | Makefile | 20 | ||||
| -rwxr-xr-x | admin/feeds/build-feed.py | 189 | ||||
| -rwxr-xr-x | admin/feeds/build-feeds.sh | 19 | ||||
| -rw-r--r-- | admin/feeds/concerts-pubdates.json | 15 | ||||
| -rwxr-xr-x | build-concerts.py | 174 | ||||
| -rwxr-xr-x | build-feed.sh | 13 | ||||
| -rw-r--r-- | en/feed.xml | 276 | ||||
| -rw-r--r-- | feed.xml | 273 | ||||
| -rw-r--r-- | helpers.py | 152 |
9 files changed, 823 insertions, 308 deletions
@@ -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 & 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 & 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 @@ -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 & 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 & 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 @@ -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'), + }, +} |
