# SPDX-License-Identifier: CC0-1.0 # koszko.org website logic. # Copyright (C) 2021, 2022 Wojtek Kosior import gettext import re import dataclasses as dc import typing as t from pathlib import Path, PurePosixPath import flask import werkzeug import jinja2 import jinja2.exceptions here = Path(__file__).resolve().parent pages = { 'index.html' } langs_short_2_long = { 'en': 'en_US', 'pl': 'pl_PL' } langs_long_2_short = dict((v, k) for k, v in langs_short_2_long.items()) default_locale = 'pl_PL' def raise_exception(msg: str) -> t.NoReturn: raise Exception(msg) post_titles_pl = { 'gospel-despite-no-enthusiasm': 'Dobra Nowina pomimo zniechęcenia', 'how-i-fought...-another-view': 'Jak walczyłem... inne spojrzenie' } post_titles_en = { 'gospel-despite-no-enthusiasm': 'Gospel despite lack of enthusiasm', 'how-i-fought...-another-view': 'How I fought... another view' } post_titles = {'en': post_titles_en, 'pl': post_titles_pl} @dc.dataclass(frozen=True) class PostData: date: str page_path: str title: str def __lt__(self, other: 'PostData') -> bool: return (other.date, self.page_path) < (self.date, other.page_path) post_filename_re = re.compile( r''' ^ (?P<date>20[0-9][0-9]-[0-1][0-9]-[0-3][0-9]) - (?P<post_identifier>.*) \.html\.jinja $ ''', re.VERBOSE ) def get_posts(lang: str) -> t.Sequence[PostData]: lang_dir = here / 'templates' / lang posts_data = [] for template_path in (lang_dir / 'posts').glob('20*'): filename_match = post_filename_re.match(template_path.name) assert filename_match is not None date = filename_match.group('date') post_identifier = filename_match.group('post_identifier') page_path = f'posts/{date}-{post_identifier}.html' title = post_titles[lang][post_identifier] posts_data.append(PostData(date=date, page_path=page_path, title=title)) return sorted(posts_data) @dc.dataclass(init=False) class Website(flask.Flask): def __init__(self) -> None: super().__init__(__name__) self.jinja_options = { **self.jinja_options, 'loader': jinja2.PackageLoader(__package__), 'autoescape': jinja2.select_autoescape(['.jinja']), 'lstrip_blocks': True, 'extensions': [ *self.jinja_options.get('extensions', []), 'jinja2.ext.i18n', 'jinja2.ext.do' ] } self.jinja_env.globals['raise'] = raise_exception self.jinja_env.globals['get_posts'] = get_posts def koszko_install_translations(self, locale: str) -> None: translations = gettext.translation( 'messages', localedir = here / 'locales', languages = [locale] ) self.jinja_env.install_gettext_translations(translations) website_app = Website() def show_page(lang_short: str, page_path: str) -> str: for segment in PurePosixPath(page_path).parts: if segment.startswith('__'): flask.abort(404) effective_page_path = '__index.html' if page_path == '' else page_path app = t.cast(Website, flask.current_app) app.koszko_install_translations(langs_short_2_long[lang_short]) for prefix in [f'{lang_short}', '']: pure_path = PurePosixPath(prefix) / f'{effective_page_path}.jinja' footer_path = pure_path.parent / f'__footer_for_{pure_path.name}' try: html = flask.render_template( str(pure_path), lang_short = lang_short, page_path = page_path, dedicated_footer_path = str(footer_path) ) break except jinja2.exceptions.TemplateNotFound: if prefix == '': raise return html for lang in langs_short_2_long.keys(): def __show_page_in_lang(page_path: str, lang_short: str = lang) -> str: return show_page(lang_short, page_path) website_app.add_url_rule( f'/{lang}/<path:page_path>', f'show_page_in_{lang}', __show_page_in_lang, methods=['GET'] ) def __show_main_page_in_lang(lang_short: str = lang) -> str: return show_page(lang_short, '') website_app.add_url_rule( f'/{lang}/', f'show_main_page_in_{lang}', __show_main_page_in_lang, methods=['GET'] ) @website_app.route('/<path:page_path>', methods=['GET']) def redirect_to_resource(page_path) -> werkzeug.Response: if page_path != '': page_path_posix = PurePosixPath(page_path) if page_path_posix.parts[0] == 'sideload': return flask.Response( "Incomplete server configuration - requests for '/sideload/' not routed as they should be.", 500 ) # Route to other stuff that was on this domain before and has been moved # under '/sideload/'. for name in ['fraktal', 'mm', 'articles/etherwall_private_chain.html']: try: page_path_posix.relative_to(PurePosixPath(name)) except ValueError: pass else: return flask.redirect(f'/sideload/{name}') # Make all resources from '/static' aliased under '/'. if not page_path.endswith('.html'): return flask.redirect(f'/static/{page_path}') # Redirect to the most suitable language version of a page. chosen_locale = flask.request.accept_languages.best_match( langs_short_2_long.values(), default = default_locale ) if chosen_locale is None: chosen_locale = default_locale lang_short = langs_long_2_short[chosen_locale] url = flask.url_for( f'.show_page_in_{lang_short}', page_path = page_path ) return flask.redirect(url) @website_app.route('/', methods=['GET']) def redirect_to_lang_main_page() -> werkzeug.Response: return redirect_to_resource('')