1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
from dataclasses import dataclass
from datetime import datetime
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):
# pathlib.Path(x).relative_to(y) cannot handle y not being under x,
# os.path.dirname('x') yields '' rather than '.'.
# 😮💨
return path.relpath(to, Path(ref).parent)
_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')))
|