build-concerts.py (5708B)
1 #!/usr/bin/env python3 2 3 from collections import OrderedDict 4 from datetime import datetime 5 from pathlib import Path 6 from sys import argv 7 8 from helpers import ( 9 DATE_FORMATTERS, 10 guess_language, 11 read_concerts, 12 relative_path, 13 split_concerts, 14 tmplocale, 15 touchup_plaintext, 16 ) 17 18 19 # TODO: change some jargon: 20 # - event => concert 21 # - canceled => warning 22 23 24 LOCALIZED_TEXT = { 25 'en': { 26 'past': 'Past concerts', 27 'next': 'Next concerts', 28 'alt': 'Illustration:', 29 'hint': 'Click on a concert to obtain more information.', 30 }, 31 'fr': { 32 'past': 'Concerts passés', 33 'next': 'Prochains concerts', 34 'alt': 'Illustration :', 35 'hint': "Cliquez sur un concert pour obtenir plus d'informations.", 36 } 37 } 38 39 THUMBNAILS_TEMPLATE = '''\ 40 <h1>{heading}</h1> 41 <div class="events {time}"> 42 {thumbnails} 43 </div>\ 44 ''' 45 46 THUMBNAIL_TEMPLATE = '''\ 47 <div class="{eventclasses}"> 48 <a class="thumbnail" href="#{eventid}"> 49 <img {pic_attributes}> 50 <p class="summary"> 51 {summary} 52 </p> 53 </a> 54 <div class="credits"> 55 <span> 56 {credits} 57 </span> 58 </div> 59 </div>\ 60 ''' 61 62 63 def format_credits(illustration): 64 attribution = illustration.source_name 65 66 if illustration.source_link is not None: 67 attribution = ( 68 f'<a href="{illustration.source_link}" target="_blank">' 69 f'{illustration.source_name}' 70 '</a>' 71 ) 72 73 if illustration.license_info is not None: 74 attribution += ' / ' + illustration.license_info.format() 75 76 return attribution 77 78 79 def format_thumbnail(concert, imgdir, lang): 80 eventclasses = ('event',) 81 with tmplocale(lang): 82 day = f'{concert.time.day} {concert.time.strftime("%B %Y")}' 83 summary = f'{concert.place}<br>{day}' 84 85 if concert.warning is not None: 86 eventclasses += ('canceled',) 87 summary = (f'<span class="canceled">{concert.warning}</span>\n' 88 f' {summary}') 89 90 alt_prefix = LOCALIZED_TEXT[lang]['alt'] 91 92 pic_attributes = OrderedDict(( 93 ('src', Path(imgdir, 'concerts', concert.illustration.file)), 94 ('alt', f'{alt_prefix} {concert.illustration.alt_text}'), 95 ('style', concert.illustration.style), 96 )) 97 98 return THUMBNAIL_TEMPLATE.format_map({ 99 'eventclasses': ' '.join(eventclasses), 100 'eventid': f'concert-{concert.time.strftime("%F")}', 101 'pic_attributes': ' '.join( 102 f'{key}="{value}"' 103 for key, value in pic_attributes.items() 104 if value is not None 105 ), 106 'summary': summary, 107 'credits': format_credits(concert.illustration) 108 }) 109 110 111 def print_thumbnails_section(concerts, imgdir, section, lang): 112 if not concerts: 113 return 114 115 thumbnails = '\n'.join( 116 format_thumbnail(c, imgdir, lang) for c in concerts 117 ) 118 119 print(THUMBNAILS_TEMPLATE.format(heading=LOCALIZED_TEXT[lang][section], 120 time=section, 121 thumbnails=thumbnails)) 122 123 124 def print_thumbnails(concerts, imgdir, lang): 125 today = datetime.fromordinal( 126 datetime.today().date().toordinal() 127 ) 128 past_concerts, next_concerts = split_concerts(concerts, today) 129 130 print('<div id="event-list">') 131 print_thumbnails_section(next_concerts, imgdir, 'next', lang) 132 print_thumbnails_section(list(reversed(past_concerts)), imgdir, 'past', lang) 133 print('</div>') 134 135 136 DETAILS_TEMPLATE = '''\ 137 <div class="{concertclasses}" id="{concertid}"> 138 {details} 139 </div>\ 140 ''' 141 142 143 def detail_block(tag, classes, content): 144 opener = f'<{tag} class="{" ".join(classes)}">' 145 closer = f'</{tag}>' 146 147 if isinstance(content, str): 148 return f' {opener}{content}{closer}' 149 150 return '\n'.join(( 151 ' '+opener, 152 *(' '+line for line in content), 153 ' '+closer, 154 )) 155 156 157 def break_lines(lines): 158 return tuple(line+'<br>' for line in lines[:-1]) + (lines[-1],) 159 160 161 def print_concert_details(concert, lang): 162 concert_id = f'concert-{concert.time.strftime("%F")}' 163 classes = ('details',) 164 blocks = [] 165 166 if concert.warning is not None: 167 classes += ('canceled',) 168 blocks.append( 169 detail_block('p', ('canceled',), concert.warning) 170 ) 171 172 with tmplocale(lang): 173 day = DATE_FORMATTERS[lang]['date'](concert.time) 174 hour = DATE_FORMATTERS[lang]['time'](concert.time) 175 176 address_lines = break_lines(concert.address.splitlines()) 177 piece_list = tuple( 178 f'<li>{touchup_plaintext(line)}</li>' 179 for line in concert.pieces.splitlines() 180 ) 181 182 blocks.extend(( 183 detail_block('p', ('detail', 'date'), day), 184 detail_block('p', ('detail', 'time'), hour), 185 detail_block('p', ('detail', 'place'), address_lines), 186 detail_block('ol', ('detail', 'program'), piece_list), 187 )) 188 189 instructions = [ 190 f' <p>{touchup_plaintext(line)}</p>' 191 for line in concert.instructions.splitlines() if line 192 ] 193 194 print(f' <div class="{" ".join(classes)}" id="{concert_id}">') 195 print('\n'.join(blocks+instructions)) 196 print(' </div>') 197 198 199 def print_details(concerts, lang): 200 print('<div id="event-details">') 201 print(f' <p class="hint">{LOCALIZED_TEXT[lang]["hint"]}</p>') 202 203 for c in concerts: 204 print_concert_details(c, lang) 205 206 print('</div>') 207 208 209 def main(concerts_src): 210 imgdir = relative_path(to='images', ref=concerts_src) 211 lang = guess_language(concerts_src) 212 213 concerts = read_concerts(concerts_src) 214 print_thumbnails(concerts, imgdir, lang) 215 print_details(concerts, lang) 216 217 218 if __name__ == '__main__': 219 main(argv[1])